This commit is contained in:
Markos Gogoulos
2026-02-01 13:21:39 +02:00
parent a77761ec35
commit ba53467033
9 changed files with 218 additions and 63 deletions

View File

@@ -1 +1 @@
VERSION = "8" VERSION = "8.07"

View File

@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { translateString } from '../utils/helpers/'; import { translateString, inEmbeddedApp } from '../utils/helpers/';
interface MediaListHeaderProps { interface MediaListHeaderProps {
title?: string; title?: string;
@@ -11,10 +11,12 @@ interface MediaListHeaderProps {
export const MediaListHeader: React.FC<MediaListHeaderProps> = (props) => { export const MediaListHeader: React.FC<MediaListHeaderProps> = (props) => {
const viewAllText = props.viewAllText || translateString('VIEW ALL'); const viewAllText = props.viewAllText || translateString('VIEW ALL');
const isEmbedMode = inEmbeddedApp();
return ( return (
<div className={(props.className ? props.className + ' ' : '') + 'media-list-header'} style={props.style}> <div className={(props.className ? props.className + ' ' : '') + 'media-list-header'} style={props.style}>
<h2>{props.title}</h2> <h2>{props.title}</h2>
{props.viewAllLink ? ( {!isEmbedMode && props.viewAllLink ? (
<h3> <h3>
{' '} {' '}
<a href={props.viewAllLink} title={viewAllText}> <a href={props.viewAllLink} title={viewAllText}>

View File

@@ -1,18 +1,50 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useMediaItem } from '../../utils/hooks/'; import { useMediaItem } from '../../utils/hooks/';
import { PositiveInteger, PositiveIntegerOrZero } from '../../utils/helpers/'; import { PositiveInteger, PositiveIntegerOrZero, inEmbeddedApp } from '../../utils/helpers/';
import { MediaItemThumbnailLink, itemClassname } from './includes/items/'; import { MediaItemThumbnailLink, itemClassname } from './includes/items/';
import { Item } from './Item'; import { Item } from './Item';
export function MediaItem(props) { export function MediaItem(props) {
const type = props.type; const type = props.type;
const isEmbedMode = inEmbeddedApp();
const [titleComponent, descriptionComponent, thumbnailUrl, UnderThumbWrapper, editMediaComponent, metaComponents, viewMediaComponent] = const [titleComponentOrig, descriptionComponent, thumbnailUrl, UnderThumbWrapperOrig, editMediaComponent, metaComponents, viewMediaComponent] =
useMediaItem({ ...props, type }); useMediaItem({ ...props, type });
// In embed mode, override components to remove links
const ItemTitle = ({ title }) => (
<h3>
<span>{title}</span>
</h3>
);
const ItemMain = ({ children }) => <div className="item-main">{children}</div>;
const titleComponent = isEmbedMode
? () => <ItemTitle title={props.title} />
: titleComponentOrig;
const UnderThumbWrapper = isEmbedMode ? ItemMain : UnderThumbWrapperOrig;
function thumbnailComponent() { function thumbnailComponent() {
if (isEmbedMode) {
// In embed mode, render thumbnail without link
const thumbStyle = thumbnailUrl ? { backgroundImage: "url('" + thumbnailUrl + "')" } : null;
return (
<div
key="item-thumb"
className={'item-thumb' + (!thumbnailUrl ? ' no-thumb' : '')}
style={thumbStyle}
>
{thumbnailUrl ? (
<div key="item-type-icon" className="item-type-icon">
<div></div>
</div>
) : null}
</div>
);
}
return <MediaItemThumbnailLink src={thumbnailUrl} title={props.title} link={props.link} />; return <MediaItemThumbnailLink src={thumbnailUrl} title={props.title} link={props.link} />;
} }
@@ -23,13 +55,15 @@ export function MediaItem(props) {
); );
const finalClassname = containerClassname + const finalClassname = containerClassname +
(props.showSelection ? ' with-selection' : '') + (props.showSelection && !isEmbedMode ? ' with-selection' : '') +
(props.isSelected ? ' selected' : '') + (props.isSelected ? ' selected' : '') +
(props.hasAnySelection ? ' has-any-selection' : ''); (props.hasAnySelection || isEmbedMode ? ' has-any-selection' : '');
const handleItemClick = (e) => { const handleItemClick = (e) => {
// If there's any selection active, clicking the item should toggle selection const isEmbedMode = inEmbeddedApp();
if (props.hasAnySelection && props.onCheckboxChange) {
// In embed mode or if there's any selection active, clicking the item should toggle selection
if ((isEmbedMode || props.hasAnySelection) && props.onCheckboxChange) {
// Check if clicking on the checkbox itself, edit icon, or view icon // Check if clicking on the checkbox itself, edit icon, or view icon
if (e.target.closest('.item-selection-checkbox') || if (e.target.closest('.item-selection-checkbox') ||
e.target.closest('.item-edit-icon') || e.target.closest('.item-edit-icon') ||
@@ -59,16 +93,24 @@ export function MediaItem(props) {
</div> </div>
)} )}
{editMediaComponent()} {!isEmbedMode && editMediaComponent()}
{viewMediaComponent()} {!isEmbedMode && viewMediaComponent()}
{thumbnailComponent()} {thumbnailComponent()}
<UnderThumbWrapper title={props.title} link={props.link}> {isEmbedMode ? (
{titleComponent()} <UnderThumbWrapper>
{metaComponents()} {titleComponent()}
{descriptionComponent()} {metaComponents()}
</UnderThumbWrapper> {descriptionComponent()}
</UnderThumbWrapper>
) : (
<UnderThumbWrapper title={props.title} link={props.link}>
{titleComponent()}
{metaComponents()}
{descriptionComponent()}
</UnderThumbWrapper>
)}
</div> </div>
</div> </div>
); );

View File

@@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useMediaItem } from '../../utils/hooks/'; import { useMediaItem } from '../../utils/hooks/';
import { PositiveIntegerOrZero } from '../../utils/helpers/'; import { PositiveIntegerOrZero, inEmbeddedApp } from '../../utils/helpers/';
import { MediaDurationInfo } from '../../utils/classes/'; import { MediaDurationInfo } from '../../utils/classes/';
import { MediaPlaylistOptions } from '../media-playlist-options/MediaPlaylistOptions'; import { MediaPlaylistOptions } from '../media-playlist-options/MediaPlaylistOptions';
import { MediaItemDuration, MediaItemPlaylistIndex, itemClassname } from './includes/items/'; import { MediaItemDuration, MediaItemPlaylistIndex, itemClassname } from './includes/items/';
@@ -9,10 +9,26 @@ import { MediaItem } from './MediaItem';
export function MediaItemAudio(props) { export function MediaItemAudio(props) {
const type = props.type; const type = props.type;
const isEmbedMode = inEmbeddedApp();
const [titleComponent, descriptionComponent, thumbnailUrl, UnderThumbWrapper, editMediaComponent, metaComponents, viewMediaComponent] = const [titleComponentOrig, descriptionComponent, thumbnailUrl, UnderThumbWrapperOrig, editMediaComponent, metaComponents, viewMediaComponent] =
useMediaItem({ ...props, type }); useMediaItem({ ...props, type });
// In embed mode, override components to remove links
const ItemTitle = ({ title }) => (
<h3>
<span>{title}</span>
</h3>
);
const ItemMain = ({ children }) => <div className="item-main">{children}</div>;
const titleComponent = isEmbedMode
? () => <ItemTitle title={props.title} />
: titleComponentOrig;
const UnderThumbWrapper = isEmbedMode ? ItemMain : UnderThumbWrapperOrig;
const _MediaDurationInfo = new MediaDurationInfo(); const _MediaDurationInfo = new MediaDurationInfo();
_MediaDurationInfo.update(props.duration); _MediaDurationInfo.update(props.duration);
@@ -22,6 +38,21 @@ export function MediaItemAudio(props) {
const durationISO8601 = _MediaDurationInfo.ISO8601(); const durationISO8601 = _MediaDurationInfo.ISO8601();
function thumbnailComponent() { function thumbnailComponent() {
if (isEmbedMode) {
// In embed mode, render thumbnail without link
return (
<div
key="item-thumb"
className={'item-thumb' + (!thumbnailUrl ? ' no-thumb' : '')}
style={!thumbnailUrl ? null : { backgroundImage: "url('" + thumbnailUrl + "')" }}
>
{props.inPlaylistView ? null : (
<MediaItemDuration ariaLabel={duration} time={durationISO8601} text={durationStr} />
)}
</div>
);
}
const attr = { const attr = {
key: 'item-thumb', key: 'item-thumb',
href: props.link, href: props.link,
@@ -66,13 +97,13 @@ export function MediaItemAudio(props) {
); );
const finalClassname = containerClassname + const finalClassname = containerClassname +
(props.showSelection ? ' with-selection' : '') + (props.showSelection && !isEmbedMode ? ' with-selection' : '') +
(props.isSelected ? ' selected' : '') + (props.isSelected ? ' selected' : '') +
(props.hasAnySelection ? ' has-any-selection' : ''); (props.hasAnySelection || isEmbedMode ? ' has-any-selection' : '');
const handleItemClick = (e) => { const handleItemClick = (e) => {
// If there's any selection active, clicking the item should toggle selection // In embed mode or if there's any selection active, clicking the item should toggle selection
if (props.hasAnySelection && props.onCheckboxChange) { if ((isEmbedMode || props.hasAnySelection) && props.onCheckboxChange) {
// Check if clicking on the checkbox itself, edit icon, or view icon // Check if clicking on the checkbox itself, edit icon, or view icon
if (e.target.closest('.item-selection-checkbox') || if (e.target.closest('.item-selection-checkbox') ||
e.target.closest('.item-edit-icon') || e.target.closest('.item-edit-icon') ||
@@ -104,16 +135,24 @@ export function MediaItemAudio(props) {
</div> </div>
)} )}
{editMediaComponent()} {!isEmbedMode && editMediaComponent()}
{viewMediaComponent()} {!isEmbedMode && viewMediaComponent()}
{thumbnailComponent()} {thumbnailComponent()}
<UnderThumbWrapper title={props.title} link={props.link}> {isEmbedMode ? (
{titleComponent()} <UnderThumbWrapper>
{metaComponents()} {titleComponent()}
{descriptionComponent()} {metaComponents()}
</UnderThumbWrapper> {descriptionComponent()}
</UnderThumbWrapper>
) : (
<UnderThumbWrapper title={props.title} link={props.link}>
{titleComponent()}
{metaComponents()}
{descriptionComponent()}
</UnderThumbWrapper>
)}
{playlistOptionsComponent()} {playlistOptionsComponent()}
</div> </div>

View File

@@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useMediaItem } from '../../utils/hooks/'; import { useMediaItem } from '../../utils/hooks/';
import { PositiveIntegerOrZero } from '../../utils/helpers/'; import { PositiveIntegerOrZero, inEmbeddedApp } from '../../utils/helpers/';
import { MediaDurationInfo } from '../../utils/classes/'; import { MediaDurationInfo } from '../../utils/classes/';
import { MediaPlaylistOptions } from '../media-playlist-options/MediaPlaylistOptions.jsx'; import { MediaPlaylistOptions } from '../media-playlist-options/MediaPlaylistOptions.jsx';
import { MediaItemVideoPlayer, MediaItemDuration, MediaItemVideoPreviewer, MediaItemPlaylistIndex, itemClassname } from './includes/items/'; import { MediaItemVideoPlayer, MediaItemDuration, MediaItemVideoPreviewer, MediaItemPlaylistIndex, itemClassname } from './includes/items/';
@@ -9,10 +9,26 @@ import { MediaItem } from './MediaItem';
export function MediaItemVideo(props) { export function MediaItemVideo(props) {
const type = props.type; const type = props.type;
const isEmbedMode = inEmbeddedApp();
const [titleComponent, descriptionComponent, thumbnailUrl, UnderThumbWrapper, editMediaComponent, metaComponents, viewMediaComponent] = const [titleComponentOrig, descriptionComponent, thumbnailUrl, UnderThumbWrapperOrig, editMediaComponent, metaComponents, viewMediaComponent] =
useMediaItem({ ...props, type }); useMediaItem({ ...props, type });
// In embed mode, override components to remove links
const ItemTitle = ({ title }) => (
<h3>
<span>{title}</span>
</h3>
);
const ItemMain = ({ children }) => <div className="item-main">{children}</div>;
const titleComponent = isEmbedMode
? () => <ItemTitle title={props.title} />
: titleComponentOrig;
const UnderThumbWrapper = isEmbedMode ? ItemMain : UnderThumbWrapperOrig;
const _MediaDurationInfo = new MediaDurationInfo(); const _MediaDurationInfo = new MediaDurationInfo();
_MediaDurationInfo.update(props.duration); _MediaDurationInfo.update(props.duration);
@@ -26,6 +42,24 @@ export function MediaItemVideo(props) {
} }
function thumbnailComponent() { function thumbnailComponent() {
if (isEmbedMode) {
// In embed mode, render thumbnail without link
return (
<div
key="item-thumb"
className={'item-thumb' + (!thumbnailUrl ? ' no-thumb' : '')}
style={!thumbnailUrl ? null : { backgroundImage: "url('" + thumbnailUrl + "')" }}
>
{props.inPlaylistView ? null : (
<MediaItemDuration ariaLabel={duration} time={durationISO8601} text={durationStr} />
)}
{props.inPlaylistView || props.inPlaylistPage ? null : (
<MediaItemVideoPreviewer url={props.preview_thumbnail} />
)}
</div>
);
}
const attr = { const attr = {
key: 'item-thumb', key: 'item-thumb',
href: props.link, href: props.link,
@@ -73,13 +107,13 @@ export function MediaItemVideo(props) {
); );
const finalClassname = containerClassname + const finalClassname = containerClassname +
(props.showSelection ? ' with-selection' : '') + (props.showSelection && !isEmbedMode ? ' with-selection' : '') +
(props.isSelected ? ' selected' : '') + (props.isSelected ? ' selected' : '') +
(props.hasAnySelection ? ' has-any-selection' : ''); (props.hasAnySelection || isEmbedMode ? ' has-any-selection' : '');
const handleItemClick = (e) => { const handleItemClick = (e) => {
// If there's any selection active, clicking the item should toggle selection // In embed mode or if there's any selection active, clicking the item should toggle selection
if (props.hasAnySelection && props.onCheckboxChange) { if ((isEmbedMode || props.hasAnySelection) && props.onCheckboxChange) {
// Check if clicking on the checkbox itself, edit icon, or view icon // Check if clicking on the checkbox itself, edit icon, or view icon
if (e.target.closest('.item-selection-checkbox') || if (e.target.closest('.item-selection-checkbox') ||
e.target.closest('.item-edit-icon') || e.target.closest('.item-edit-icon') ||
@@ -111,19 +145,27 @@ export function MediaItemVideo(props) {
</div> </div>
)} )}
{editMediaComponent()} {!isEmbedMode && editMediaComponent()}
{viewMediaComponent()} {!isEmbedMode && viewMediaComponent()}
{props.hasMediaViewer ? videoViewerComponent() : thumbnailComponent()} {props.hasMediaViewer ? videoViewerComponent() : thumbnailComponent()}
<UnderThumbWrapper title={props.title} link={props.link}> {isEmbedMode ? (
{titleComponent()} <UnderThumbWrapper>
{metaComponents()} {titleComponent()}
{descriptionComponent()} {metaComponents()}
</UnderThumbWrapper> {descriptionComponent()}
</div> </UnderThumbWrapper>
) : (
<UnderThumbWrapper title={props.title} link={props.link}>
{titleComponent()}
{metaComponents()}
{descriptionComponent()}
</UnderThumbWrapper>
)}
{playlistOptionsComponent()} {playlistOptionsComponent()}
</div>
</div> </div>
); );
} }

View File

@@ -5,7 +5,7 @@ import { LinksContext, MemberContext, SiteContext } from '../../utils/contexts/'
import { PageStore, ProfilePageStore } from '../../utils/stores/'; import { PageStore, ProfilePageStore } from '../../utils/stores/';
import { PageActions, ProfilePageActions } from '../../utils/actions/'; import { PageActions, ProfilePageActions } from '../../utils/actions/';
import { CircleIconButton, PopupMain } from '../_shared'; import { CircleIconButton, PopupMain } from '../_shared';
import { translateString } from '../../utils/helpers/'; import { translateString, inEmbeddedApp } from '../../utils/helpers/';
class ProfileSearchBar extends React.PureComponent { class ProfileSearchBar extends React.PureComponent {
constructor(props) { constructor(props) {
@@ -372,18 +372,22 @@ class NavMenuInlineTabs extends React.PureComponent {
} }
render() { render() {
const isEmbedMode = inEmbeddedApp();
return ( return (
<nav ref="tabsNav" className="profile-nav items-list-outer list-inline list-slider"> <nav ref="tabsNav" className="profile-nav items-list-outer list-inline list-slider">
<div className="profile-nav-inner items-list-outer"> <div className="profile-nav-inner items-list-outer">
{this.state.displayPrev ? this.previousBtn : null} {this.state.displayPrev ? this.previousBtn : null}
<ul className="items-list-wrap" ref="itemsListWrap"> <ul className="items-list-wrap" ref="itemsListWrap">
<InlineTab {!isEmbedMode ? (
id="about" <InlineTab
isActive={'about' === this.props.type} id="about"
label={translateString('About')} isActive={'about' === this.props.type}
link={LinksContext._currentValue.profile.about} label={translateString('About')}
/> link={LinksContext._currentValue.profile.about}
/>
) : null}
<InlineTab <InlineTab
id="media" id="media"
isActive={'media' === this.props.type} isActive={'media' === this.props.type}
@@ -407,7 +411,7 @@ class NavMenuInlineTabs extends React.PureComponent {
/> />
) : null} ) : null}
{MemberContext._currentValue.can.saveMedia ? ( {!isEmbedMode && MemberContext._currentValue.can.saveMedia ? (
<InlineTab <InlineTab
id="playlists" id="playlists"
isActive={'playlists' === this.props.type} isActive={'playlists' === this.props.type}
@@ -768,7 +772,15 @@ export default function ProfilePagesHeader(props) {
)} )}
<div className="profile-info-nav-wrap"> <div className="profile-info-nav-wrap">
{props.author.thumbnail_url || props.author.name ? ( {inEmbeddedApp() ? (
<div className="profile-info">
<div className="profile-info-inner">
<div>
<h1>{translateString('Embed Media')}</h1>
</div>
</div>
</div>
) : props.author.thumbnail_url || props.author.name ? (
<div className="profile-info"> <div className="profile-info">
<div className="profile-info-inner"> <div className="profile-info-inner">
<div> <div>

View File

@@ -202,13 +202,29 @@ export class ProfileMediaPage extends Page {
} }
handleMediaSelection(mediaId, isSelected) { handleMediaSelection(mediaId, isSelected) {
const isEmbedMode = inEmbeddedApp();
this.setState((prevState) => { this.setState((prevState) => {
const newSelectedMedia = new Set(prevState.selectedMedia); const newSelectedMedia = new Set();
if (isSelected) {
newSelectedMedia.add(mediaId); // In embed mode, only allow single selection
if (isEmbedMode) {
if (isSelected) {
newSelectedMedia.add(mediaId);
console.log('Selected media item:', mediaId);
}
} else { } else {
newSelectedMedia.delete(mediaId); // Normal mode: allow multiple selection
newSelectedMedia.clear();
prevState.selectedMedia.forEach((id) => newSelectedMedia.add(id));
if (isSelected) {
newSelectedMedia.add(mediaId);
} else {
newSelectedMedia.delete(mediaId);
}
} }
return { selectedMedia: newSelectedMedia }; return { selectedMedia: newSelectedMedia };
}); });
} }
@@ -917,6 +933,7 @@ export class ProfileMediaPage extends Page {
const authorData = ProfilePageStore.get('author-data'); const authorData = ProfilePageStore.get('author-data');
const isMediaAuthor = authorData && authorData.username === MemberContext._currentValue.username; const isMediaAuthor = authorData && authorData.username === MemberContext._currentValue.username;
const isEmbedMode = inEmbeddedApp();
// Check if any filters are active (excluding default sort and tags) // Check if any filters are active (excluding default sort and tags)
const hasActiveFilters = const hasActiveFilters =
@@ -948,15 +965,16 @@ export class ProfileMediaPage extends Page {
this.state.author ? ( this.state.author ? (
<ProfilePagesContent key="ProfilePagesContent"> <ProfilePagesContent key="ProfilePagesContent">
<MediaListWrapper <MediaListWrapper
title={this.state.title} title={isEmbedMode ? undefined : this.state.title}
className="items-list-ver" className="items-list-ver"
showBulkActions={isMediaAuthor} style={isEmbedMode ? { marginTop: '24px' } : undefined}
showBulkActions={!isEmbedMode && isMediaAuthor}
selectedCount={this.state.selectedMedia.size} selectedCount={this.state.selectedMedia.size}
totalCount={this.state.availableMediaIds.length} totalCount={this.state.availableMediaIds.length}
onBulkAction={this.handleBulkAction} onBulkAction={this.handleBulkAction}
onSelectAll={this.handleSelectAll} onSelectAll={this.handleSelectAll}
onDeselectAll={this.handleDeselectAll} onDeselectAll={this.handleDeselectAll}
showAddMediaButton={isMediaAuthor} showAddMediaButton={!isEmbedMode && isMediaAuthor}
> >
<ProfileMediaFilters <ProfileMediaFilters
hidden={this.state.hiddenFilters} hidden={this.state.hiddenFilters}
@@ -979,7 +997,7 @@ export class ProfileMediaPage extends Page {
hideViews={!PageStore.get('config-media-item').displayViews} hideViews={!PageStore.get('config-media-item').displayViews}
hideDate={!PageStore.get('config-media-item').displayPublishDate} hideDate={!PageStore.get('config-media-item').displayPublishDate}
canEdit={isMediaAuthor} canEdit={isMediaAuthor}
showSelection={isMediaAuthor} showSelection={isMediaAuthor || isEmbedMode}
hasAnySelection={this.state.selectedMedia.size > 0} hasAnySelection={this.state.selectedMedia.size > 0}
selectedMedia={this.state.selectedMedia} selectedMedia={this.state.selectedMedia}
onMediaSelection={this.handleMediaSelection} onMediaSelection={this.handleMediaSelection}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long