import React from 'react'; import PropTypes from 'prop-types'; import { ApiUrlContext, LinksConsumer, MemberContext } from '../utils/contexts'; import { PageStore, ProfilePageStore } from '../utils/stores'; import { ProfilePageActions, PageActions } from '../utils/actions'; import { translateString } from '../utils/helpers/'; import { MediaListWrapper } from '../components/MediaListWrapper'; import ProfilePagesHeader from '../components/profile-page/ProfilePagesHeader'; import ProfilePagesContent from '../components/profile-page/ProfilePagesContent'; import { LazyLoadItemListAsync } from '../components/item-list/LazyLoadItemListAsync'; import { BulkActionConfirmModal } from '../components/BulkActionConfirmModal'; import { BulkActionPermissionModal } from '../components/BulkActionPermissionModal'; import { BulkActionPlaylistModal } from '../components/BulkActionPlaylistModal'; import { BulkActionChangeOwnerModal } from '../components/BulkActionChangeOwnerModal'; import { BulkActionPublishStateModal } from '../components/BulkActionPublishStateModal'; import { BulkActionCategoryModal } from '../components/BulkActionCategoryModal'; import { BulkActionTagModal } from '../components/BulkActionTagModal'; import { ProfileMediaFilters } from '../components/search-filters/ProfileMediaFilters'; import { ProfileMediaTags } from '../components/search-filters/ProfileMediaTags'; import { ProfileMediaSorting } from '../components/search-filters/ProfileMediaSorting'; import { Page } from './_Page'; import '../components/profile-page/ProfilePage.scss'; export class ProfileMediaPage extends Page { constructor(props, pageSlug) { super(props, 'string' === typeof pageSlug ? pageSlug : 'author-home'); this.profilePageSlug = 'string' === typeof pageSlug ? pageSlug : 'author-home'; this.state = { channelMediaCount: -1, author: ProfilePageStore.get('author-data'), uploadsPreviewItemsCount: 0, title: this.props.title, query: ProfilePageStore.get('author-query'), requestUrl: null, selectedMedia: new Set(), availableMediaIds: [], showConfirmModal: false, pendingAction: null, confirmMessage: '', listKey: 0, notificationMessage: '', showNotification: false, notificationType: 'success', hiddenFilters: true, hiddenTags: true, hiddenSorting: true, filterArgs: '', availableTags: [], selectedTag: 'all', selectedSort: 'date_added_desc', showPermissionModal: false, permissionType: null, showPlaylistModal: false, showChangeOwnerModal: false, showPublishStateModal: false, showCategoryModal: false, showTagModal: false, }; this.authorDataLoad = this.authorDataLoad.bind(this); this.onAuthorPreviewItemsCountCallback = this.onAuthorPreviewItemsCountCallback.bind(this); this.getCountFunc = this.getCountFunc.bind(this); this.changeRequestQuery = this.changeRequestQuery.bind(this); this.handleMediaSelection = this.handleMediaSelection.bind(this); this.handleBulkAction = this.handleBulkAction.bind(this); this.handleConfirmCancel = this.handleConfirmCancel.bind(this); this.handleConfirmProceed = this.handleConfirmProceed.bind(this); this.clearSelectionAndRefresh = this.clearSelectionAndRefresh.bind(this); this.clearSelection = this.clearSelection.bind(this); this.executeEnableComments = this.executeEnableComments.bind(this); this.executeDisableComments = this.executeDisableComments.bind(this); this.executeEnableDownload = this.executeEnableDownload.bind(this); this.executeDisableDownload = this.executeDisableDownload.bind(this); this.executeCopyMedia = this.executeCopyMedia.bind(this); this.showNotification = this.showNotification.bind(this); this.handleSelectAll = this.handleSelectAll.bind(this); this.handleDeselectAll = this.handleDeselectAll.bind(this); this.handleItemsUpdate = this.handleItemsUpdate.bind(this); this.onToggleFiltersClick = this.onToggleFiltersClick.bind(this); this.onToggleTagsClick = this.onToggleTagsClick.bind(this); this.onToggleSortingClick = this.onToggleSortingClick.bind(this); this.onFiltersUpdate = this.onFiltersUpdate.bind(this); this.onTagSelect = this.onTagSelect.bind(this); this.onSortSelect = this.onSortSelect.bind(this); this.onResponseDataLoaded = this.onResponseDataLoaded.bind(this); this.handlePermissionModalCancel = this.handlePermissionModalCancel.bind(this); this.handlePermissionModalSuccess = this.handlePermissionModalSuccess.bind(this); this.handlePermissionModalError = this.handlePermissionModalError.bind(this); this.handlePlaylistModalCancel = this.handlePlaylistModalCancel.bind(this); this.handlePlaylistModalSuccess = this.handlePlaylistModalSuccess.bind(this); this.handlePlaylistModalError = this.handlePlaylistModalError.bind(this); this.handleChangeOwnerModalCancel = this.handleChangeOwnerModalCancel.bind(this); this.handleChangeOwnerModalSuccess = this.handleChangeOwnerModalSuccess.bind(this); this.handleChangeOwnerModalError = this.handleChangeOwnerModalError.bind(this); this.handlePublishStateModalCancel = this.handlePublishStateModalCancel.bind(this); this.handlePublishStateModalSuccess = this.handlePublishStateModalSuccess.bind(this); this.handlePublishStateModalError = this.handlePublishStateModalError.bind(this); this.handleCategoryModalCancel = this.handleCategoryModalCancel.bind(this); this.handleCategoryModalSuccess = this.handleCategoryModalSuccess.bind(this); this.handleCategoryModalError = this.handleCategoryModalError.bind(this); this.handleTagModalCancel = this.handleTagModalCancel.bind(this); this.handleTagModalSuccess = this.handleTagModalSuccess.bind(this); this.handleTagModalError = this.handleTagModalError.bind(this); ProfilePageStore.on('load-author-data', this.authorDataLoad); } componentDidMount() { ProfilePageActions.load_author_data(); } authorDataLoad() { const author = ProfilePageStore.get('author-data'); let requestUrl = this.state.requestUrl; if (author) { if (this.state.query) { requestUrl = ApiUrlContext._currentValue.media + '?author=' + author.id + '&q=' + encodeURIComponent(this.state.query) + this.state.filterArgs; } else { requestUrl = ApiUrlContext._currentValue.media + '?author=' + author.id + this.state.filterArgs; } } this.setState({ author: author, requestUrl: requestUrl, }); } onAuthorPreviewItemsCountCallback(totalAuthorPreviewItems) { this.setState({ uploadsPreviewItemsCount: totalAuthorPreviewItems, }); } getCountFunc(count) { this.setState( { channelMediaCount: count, }, () => { if (this.state.query) { let title = ''; if (!count) { title = translateString('No results for') + ' "' + this.state.query + '"'; } else if (1 === count) { title = translateString('1 result for') + ' "' + this.state.query + '"'; } else { title = count + ' ' + translateString('results for') + ' "' + this.state.query + '"'; } this.setState({ title: title, }); } } ); } changeRequestQuery(newQuery) { if (!this.state.author) { return; } let requestUrl; if (newQuery) { requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + '&q=' + encodeURIComponent(newQuery) + this.state.filterArgs; } else { requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + this.state.filterArgs; } let title = this.state.title; if ('' === newQuery) { title = this.props.title; } this.setState({ requestUrl: requestUrl, query: newQuery, title: title, }); } handleMediaSelection(mediaId, isSelected) { this.setState((prevState) => { const newSelectedMedia = new Set(prevState.selectedMedia); if (isSelected) { newSelectedMedia.add(mediaId); } else { newSelectedMedia.delete(mediaId); } return { selectedMedia: newSelectedMedia }; }); } handleBulkAction(action) { const selectedCount = this.state.selectedMedia.size; if (selectedCount === 0) { return; } if (action === 'delete-media') { this.setState({ showConfirmModal: true, pendingAction: action, confirmMessage: translateString('You are going to delete') + ` ${selectedCount} ` + translateString('media, are you sure?'), }); } else if (action === 'enable-comments') { this.setState({ showConfirmModal: true, pendingAction: action, confirmMessage: translateString('You are going to enable comments to') + ` ${selectedCount} ` + translateString('media, are you sure?'), }); } else if (action === 'disable-comments') { this.setState({ showConfirmModal: true, pendingAction: action, confirmMessage: translateString('You are going to disable comments to') + ` ${selectedCount} ` + translateString('media, are you sure?'), }); } else if (action === 'enable-download') { this.setState({ showConfirmModal: true, pendingAction: action, confirmMessage: translateString('You are going to enable download for') + ` ${selectedCount} ` + translateString('media, are you sure?'), }); } else if (action === 'disable-download') { this.setState({ showConfirmModal: true, pendingAction: action, confirmMessage: translateString('You are going to disable download for') + ` ${selectedCount} ` + translateString('media, are you sure?'), }); } else if (action === 'copy-media') { this.setState({ showConfirmModal: true, pendingAction: action, confirmMessage: translateString('You are going to copy') + ` ${selectedCount} ` + translateString('media, are you sure?'), }); } else if (action === 'add-remove-coviewers') { this.setState({ showPermissionModal: true, permissionType: 'viewer', }); } else if (action === 'add-remove-coeditors') { this.setState({ showPermissionModal: true, permissionType: 'editor', }); } else if (action === 'add-remove-coowners') { this.setState({ showPermissionModal: true, permissionType: 'owner', }); } else if (action === 'add-remove-playlist') { this.setState({ showPlaylistModal: true, }); } else if (action === 'change-owner') { this.setState({ showChangeOwnerModal: true, }); } else if (action === 'publish-state') { this.setState({ showPublishStateModal: true, }); } else if (action === 'add-remove-category') { this.setState({ showCategoryModal: true, }); } else if (action === 'add-remove-tags') { this.setState({ showTagModal: true, }); } else { // Other actions can be implemented later } } handleConfirmCancel() { this.setState({ showConfirmModal: false, pendingAction: null, confirmMessage: '', }); } handleConfirmProceed() { const action = this.state.pendingAction; this.setState({ showConfirmModal: false, pendingAction: null, confirmMessage: '', }); if (action === 'delete-media') { this.executeDeleteMedia(); } else if (action === 'enable-comments') { this.executeEnableComments(); } else if (action === 'disable-comments') { this.executeDisableComments(); } else if (action === 'enable-download') { this.executeEnableDownload(); } else if (action === 'disable-download') { this.executeDisableDownload(); } else if (action === 'copy-media') { this.executeCopyMedia(); } } executeDeleteMedia() { const selectedIds = Array.from(this.state.selectedMedia); const selectedCount = selectedIds.length; fetch('/api/v1/media/user/bulk_actions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': this.getCsrfToken(), }, body: JSON.stringify({ action: 'delete_media', media_ids: selectedIds, }), }) .then((response) => { if (!response.ok) { throw new Error('Failed to delete media'); } return response.json(); }) .then((data) => { const message = selectedCount === 1 ? translateString('The media was deleted successfully.') : translateString('Successfully deleted') + ` ${selectedCount} ` + translateString('media.'); this.showNotification(message); this.clearSelectionAndRefresh(); }) .catch((error) => { this.showNotification(translateString('Failed to delete media. Please try again.'), 'error'); this.clearSelectionAndRefresh(); }); } getCsrfToken() { const name = 'csrftoken'; let cookieValue = null; if (document.cookie && document.cookie !== '') { const cookies = document.cookie.split(';'); for (let i = 0; i < cookies.length; i++) { const cookie = cookies[i].trim(); if (cookie.substring(0, name.length + 1) === name + '=') { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); break; } } } return cookieValue; } clearSelectionAndRefresh() { // Clear selected media and increment listKey to force re-render this.setState((prevState) => ({ selectedMedia: new Set(), listKey: prevState.listKey + 1, })); } clearSelection() { // Clear selected media without refreshing this.setState({ selectedMedia: new Set(), }); } executeEnableComments() { const selectedIds = Array.from(this.state.selectedMedia); fetch('/api/v1/media/user/bulk_actions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': this.getCsrfToken(), }, body: JSON.stringify({ action: 'enable_comments', media_ids: selectedIds, }), }) .then((response) => { if (!response.ok) { throw new Error('Failed to enable comments'); } return response.json(); }) .then((data) => { this.showNotification(translateString('Successfully Enabled comments')); this.clearSelection(); }) .catch((error) => { this.showNotification(translateString('Failed to enable comments.'), 'error'); this.clearSelection(); }); } executeDisableComments() { const selectedIds = Array.from(this.state.selectedMedia); fetch('/api/v1/media/user/bulk_actions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': this.getCsrfToken(), }, body: JSON.stringify({ action: 'disable_comments', media_ids: selectedIds, }), }) .then((response) => { if (!response.ok) { throw new Error('Failed to disable comments'); } return response.json(); }) .then((data) => { this.showNotification(translateString('Successfully Disabled comments')); this.clearSelection(); }) .catch((error) => { this.showNotification(translateString('Failed to disable comments.'), 'error'); this.clearSelection(); }); } executeEnableDownload() { const selectedIds = Array.from(this.state.selectedMedia); fetch('/api/v1/media/user/bulk_actions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': this.getCsrfToken(), }, body: JSON.stringify({ action: 'enable_download', media_ids: selectedIds, }), }) .then((response) => { if (!response.ok) { throw new Error('Failed to enable download'); } return response.json(); }) .then((data) => { this.showNotification(translateString('Successfully Enabled Download')); this.clearSelection(); }) .catch((error) => { this.showNotification(translateString('Failed to enable download.'), 'error'); this.clearSelection(); }); } executeDisableDownload() { const selectedIds = Array.from(this.state.selectedMedia); fetch('/api/v1/media/user/bulk_actions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': this.getCsrfToken(), }, body: JSON.stringify({ action: 'disable_download', media_ids: selectedIds, }), }) .then((response) => { if (!response.ok) { throw new Error('Failed to disable download'); } return response.json(); }) .then((data) => { this.showNotification(translateString('Successfully Disabled Download')); this.clearSelection(); }) .catch((error) => { this.showNotification(translateString('Failed to disable download.'), 'error'); this.clearSelection(); }); } executeCopyMedia() { const selectedIds = Array.from(this.state.selectedMedia); fetch('/api/v1/media/user/bulk_actions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': this.getCsrfToken(), }, body: JSON.stringify({ action: 'copy_media', media_ids: selectedIds, }), }) .then((response) => { if (!response.ok) { throw new Error('Failed to copy media'); } return response.json(); }) .then((data) => { this.showNotification(translateString('Successfully Copied')); this.clearSelectionAndRefresh(); }) .catch((error) => { this.showNotification(translateString('Failed to copy media.'), 'error'); this.clearSelection(); }); } showNotification(message, type = 'success') { this.setState({ notificationMessage: message, showNotification: true, notificationType: type, }); setTimeout(() => { this.setState({ showNotification: false }); }, 5000); } handleItemsUpdate(items) { // Extract media IDs from loaded items const mediaIds = items.map((item) => item.friendly_token || item.uid || item.id); this.setState({ availableMediaIds: mediaIds }); } handleSelectAll() { // Select all available media this.setState({ selectedMedia: new Set(this.state.availableMediaIds), }); } handleDeselectAll() { // Clear all selections this.setState({ selectedMedia: new Set(), }); } onToggleFiltersClick() { this.setState({ hiddenFilters: !this.state.hiddenFilters, hiddenTags: true, hiddenSorting: true, }); } onToggleTagsClick() { this.setState({ hiddenFilters: true, hiddenTags: !this.state.hiddenTags, hiddenSorting: true, }); } onToggleSortingClick() { this.setState({ hiddenFilters: true, hiddenTags: true, hiddenSorting: !this.state.hiddenSorting, }); } onTagSelect(tag) { this.setState({ selectedTag: tag }, () => { // Apply tag filter this.onFiltersUpdate({ media_type: this.state.filterArgs.includes('media_type') ? this.state.filterArgs.match(/media_type=([^&]*)/)?.[1] : null, upload_date: this.state.filterArgs.includes('upload_date') ? this.state.filterArgs.match(/upload_date=([^&]*)/)?.[1] : null, sort_by: this.state.selectedSort, tag: tag, }); }); } onSortSelect(sortOption) { this.setState({ selectedSort: sortOption }, () => { // Apply sort filter this.onFiltersUpdate({ media_type: this.state.filterArgs.includes('media_type') ? this.state.filterArgs.match(/media_type=([^&]*)/)?.[1] : null, upload_date: this.state.filterArgs.includes('upload_date') ? this.state.filterArgs.match(/upload_date=([^&]*)/)?.[1] : null, sort_by: sortOption, tag: this.state.selectedTag, }); }); } onFiltersUpdate(updatedArgs) { const args = { media_type: null, upload_date: null, duration: null, publish_state: null, sort_by: null, ordering: null, t: null, }; switch (updatedArgs.media_type) { case 'video': case 'audio': case 'image': case 'pdf': args.media_type = updatedArgs.media_type; break; } switch (updatedArgs.upload_date) { case 'today': case 'this_week': case 'this_month': case 'this_year': args.upload_date = updatedArgs.upload_date; break; } // Handle duration filter if (updatedArgs.duration && updatedArgs.duration !== 'all') { args.duration = updatedArgs.duration; } // Handle publish state filter if (updatedArgs.publish_state && updatedArgs.publish_state !== 'all') { args.publish_state = updatedArgs.publish_state; } switch (updatedArgs.sort_by) { case 'date_added_desc': // Default sorting, no need to add parameters break; case 'date_added_asc': args.ordering = 'asc'; break; case 'alphabetically_asc': args.sort_by = 'title_asc'; break; case 'alphabetically_desc': args.sort_by = 'title_desc'; break; case 'plays_least': args.sort_by = 'views_asc'; break; case 'plays_most': args.sort_by = 'views_desc'; break; case 'likes_least': args.sort_by = 'likes_asc'; break; case 'likes_most': args.sort_by = 'likes_desc'; break; } // Handle tag filter if (updatedArgs.tag && updatedArgs.tag !== 'all') { args.t = updatedArgs.tag; } const newArgs = []; for (let arg in args) { if (null !== args[arg]) { newArgs.push(arg + '=' + args[arg]); } } this.setState( { filterArgs: newArgs.length ? '&' + newArgs.join('&') : '', selectedMedia: new Set(), // Clear selected items when filter changes }, function () { // Update the request URL with new filter args if (!this.state.author) { return; } let requestUrl; if (this.state.query) { requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + '&q=' + encodeURIComponent(this.state.query) + this.state.filterArgs; } else { requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + this.state.filterArgs; } this.setState({ requestUrl: requestUrl, }); } ); } handlePermissionModalCancel() { this.setState({ showPermissionModal: false, permissionType: null, }); } handlePermissionModalSuccess(message) { this.showNotification(message); this.clearSelection(); this.setState({ showPermissionModal: false, permissionType: null, }); } handlePermissionModalError(message) { this.showNotification(message, 'error'); this.setState({ showPermissionModal: false, permissionType: null, }); } handlePlaylistModalCancel() { this.setState({ showPlaylistModal: false, }); } handlePlaylistModalSuccess(message) { this.showNotification(message); this.clearSelection(); this.setState({ showPlaylistModal: false, }); } handlePlaylistModalError(message) { this.showNotification(message, 'error'); this.setState({ showPlaylistModal: false, }); } handleChangeOwnerModalCancel() { this.setState({ showChangeOwnerModal: false, }); } handleChangeOwnerModalSuccess(message) { this.showNotification(message); this.clearSelectionAndRefresh(); this.setState({ showChangeOwnerModal: false, }); } handleChangeOwnerModalError(message) { this.showNotification(message, 'error'); this.setState({ showChangeOwnerModal: false, }); } handlePublishStateModalCancel() { this.setState({ showPublishStateModal: false, }); } handlePublishStateModalSuccess(message) { this.showNotification(message); this.clearSelectionAndRefresh(); this.setState({ showPublishStateModal: false, }); } handlePublishStateModalError(message) { this.showNotification(message, 'error'); this.setState({ showPublishStateModal: false, }); } handleCategoryModalCancel() { this.setState({ showCategoryModal: false, }); } handleCategoryModalSuccess(message) { this.showNotification(message); this.clearSelection(); this.setState({ showCategoryModal: false, }); } handleCategoryModalError(message) { this.showNotification(message, 'error'); this.setState({ showCategoryModal: false, }); } handleTagModalCancel() { this.setState({ showTagModal: false, }); } handleTagModalSuccess(message) { this.showNotification(message); this.clearSelection(); this.setState({ showTagModal: false, }); } handleTagModalError(message) { this.showNotification(message, 'error'); this.setState({ showTagModal: false, }); } onResponseDataLoaded(responseData) { // Extract tags from response if (responseData && responseData.tags) { const tags = responseData.tags.split(',').map((tag) => tag.trim()).filter((tag) => tag); this.setState({ availableTags: tags }); } } pageContent() { const authorData = ProfilePageStore.get('author-data'); const isMediaAuthor = authorData && authorData.username === MemberContext._currentValue.username; // Check if any filters are active (excluding default sort and tags) const hasActiveFilters = this.state.filterArgs && ( this.state.filterArgs.includes('media_type=') || this.state.filterArgs.includes('upload_date=') || this.state.filterArgs.includes('duration=') || this.state.filterArgs.includes('publish_state=') ); const hasActiveTags = this.state.selectedTag && this.state.selectedTag !== 'all'; const hasActiveSort = this.state.selectedSort && this.state.selectedSort !== 'date_added_desc'; console.log('Filter Debug:', { filterArgs: this.state.filterArgs, selectedTag: this.state.selectedTag, selectedSort: this.state.selectedSort, hasActiveFilters, hasActiveTags, hasActiveSort }); return [ this.state.author ? ( ) : null, this.state.author ? ( ) : null, , , , , , , , this.state.showNotification ? (
{this.state.notificationMessage}
) : null, ]; } } ProfileMediaPage.propTypes = { title: PropTypes.string.isRequired, }; ProfileMediaPage.defaultProps = { title: 'Uploads', };