mirror of
https://github.com/mediacms-io/mediacms.git
synced 2026-03-09 14:37:22 -04:00
a
This commit is contained in:
@@ -9,14 +9,15 @@ interface BulkActionsDropdownProps {
|
||||
}
|
||||
|
||||
const BULK_ACTIONS = [
|
||||
{ value: 'add-remove-coviewers', label: translateString('Add / Remove Co-Viewers'), enabled: true },
|
||||
{ value: 'add-remove-coeditors', label: translateString('Add / Remove Co-Editors'), enabled: true },
|
||||
{ value: 'add-remove-coowners', label: translateString('Add / Remove Co-Owners'), enabled: true },
|
||||
{ value: 'add-remove-coviewers', label: inEmbeddedApp() ? translateString('Share with Co-Viewers') : translateString('Add / Remove Co-Viewers'), enabled: true },
|
||||
{ value: 'add-remove-coeditors', label: inEmbeddedApp() ? translateString('Share with Co-Editors') : translateString('Add / Remove Co-Editors'), enabled: true },
|
||||
{ value: 'add-remove-coowners', label: inEmbeddedApp() ? translateString('Share with Co-Owners') : translateString('Add / Remove Co-Owners'), enabled: true },
|
||||
{ value: 'add-remove-playlist', label: translateString('Add to / Remove from Playlist'), enabled: true },
|
||||
{ value: 'add-remove-category', label: inEmbeddedApp() ? translateString('Share with Course') : translateString('Add to / Remove from Category'), enabled: true },
|
||||
{ value: 'add-remove-category', label: inEmbeddedApp() ? translateString('Share with Course Members') : translateString('Add to / Remove from Category'), enabled: true },
|
||||
{ value: 'add-remove-tags', label: translateString('Add / Remove Tags'), enabled: true },
|
||||
{ value: 'enable-comments', label: translateString('Enable Comments'), enabled: true },
|
||||
{ value: 'disable-comments', label: translateString('Disable Comments'), enabled: true },
|
||||
{ value: 'delete-comments', label: translateString('Delete Comments'), enabled: true },
|
||||
{ value: 'enable-download', label: translateString('Enable Download'), enabled: true },
|
||||
{ value: 'disable-download', label: translateString('Disable Download'), enabled: true },
|
||||
{ value: 'publish-state', label: translateString('Publish State'), enabled: true },
|
||||
|
||||
@@ -8,7 +8,7 @@ import { PageStore, ProfilePageStore } from '../utils/stores/';
|
||||
import ProfilePagesHeader from '../components/profile-page/ProfilePagesHeader';
|
||||
import ProfilePagesContent from '../components/profile-page/ProfilePagesContent';
|
||||
import { MediaListRow } from '../components/MediaListRow';
|
||||
import { ProfileMediaPage } from './ProfileMediaPage';
|
||||
import { ProfileMediaPageBase } from './ProfileMediaPage';
|
||||
|
||||
class ChannelContactForm extends React.PureComponent {
|
||||
constructor(props) {
|
||||
@@ -149,7 +149,7 @@ class ChannelContactForm extends React.PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
export class ProfileAboutPage extends ProfileMediaPage {
|
||||
export class ProfileAboutPage extends ProfileMediaPageBase {
|
||||
constructor(props) {
|
||||
super(props, 'author-about');
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@ 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.jsx';
|
||||
import { ProfileMediaPage } from './ProfileMediaPage';
|
||||
import { ProfileMediaPageBase } from './ProfileMediaPage';
|
||||
|
||||
export class ProfileHistoryPage extends ProfileMediaPage {
|
||||
export class ProfileHistoryPage extends ProfileMediaPageBase {
|
||||
constructor(props) {
|
||||
super(props, 'author-history');
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@ 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 { ProfileMediaPage } from './ProfileMediaPage';
|
||||
import { ProfileMediaPageBase } from './ProfileMediaPage';
|
||||
|
||||
export class ProfileLikedPage extends ProfileMediaPage {
|
||||
export class ProfileLikedPage extends ProfileMediaPageBase {
|
||||
constructor(props) {
|
||||
super(props, 'author-liked');
|
||||
|
||||
|
||||
@@ -8,22 +8,17 @@ 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 { BulkActionsModals } from '../components/BulkActionsModals';
|
||||
import { ProfileMediaFilters } from '../components/search-filters/ProfileMediaFilters';
|
||||
import { ProfileMediaTags } from '../components/search-filters/ProfileMediaTags';
|
||||
import { ProfileMediaSorting } from '../components/search-filters/ProfileMediaSorting';
|
||||
import { withBulkActions } from '../utils/hoc/withBulkActions';
|
||||
|
||||
import { Page } from './_Page';
|
||||
|
||||
import '../components/profile-page/ProfilePage.scss';
|
||||
|
||||
export class ProfileMediaPage extends Page {
|
||||
class ProfileMediaPage extends Page {
|
||||
constructor(props, pageSlug) {
|
||||
super(props, 'string' === typeof pageSlug ? pageSlug : 'author-home');
|
||||
|
||||
@@ -36,15 +31,7 @@ export class ProfileMediaPage extends Page {
|
||||
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',
|
||||
selectedMedia: new Set(), // For select media embed mode only
|
||||
hiddenFilters: true,
|
||||
hiddenTags: true,
|
||||
hiddenSorting: true,
|
||||
@@ -52,13 +39,6 @@ export class ProfileMediaPage extends Page {
|
||||
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);
|
||||
@@ -66,20 +46,6 @@ export class ProfileMediaPage extends Page {
|
||||
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);
|
||||
@@ -87,24 +53,6 @@ export class ProfileMediaPage extends Page {
|
||||
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);
|
||||
}
|
||||
@@ -202,42 +150,22 @@ export class ProfileMediaPage extends Page {
|
||||
}
|
||||
|
||||
handleMediaSelection(mediaId, isSelected) {
|
||||
const isSelectMediaMode = inSelectMediaEmbedMode();
|
||||
|
||||
// Only used in select media embed mode; normal mode is handled by bulkActions
|
||||
this.setState((prevState) => {
|
||||
const newSelectedMedia = new Set();
|
||||
|
||||
// In select media mode, only allow single selection
|
||||
if (isSelectMediaMode) {
|
||||
if (isSelected) {
|
||||
newSelectedMedia.add(mediaId);
|
||||
console.log('Selected media item:', mediaId);
|
||||
if (isSelected) {
|
||||
newSelectedMedia.add(mediaId);
|
||||
|
||||
// Send postMessage to parent window (Moodle TinyMCE plugin)
|
||||
if (window.parent !== window) {
|
||||
// Construct the embed URL
|
||||
const baseUrl = window.location.origin;
|
||||
const embedUrl = `${baseUrl}/embed?m=${mediaId}`;
|
||||
if (window.parent !== window) {
|
||||
const baseUrl = window.location.origin;
|
||||
const embedUrl = `${baseUrl}/embed?m=${mediaId}`;
|
||||
|
||||
// Send message in the format expected by the Moodle plugin
|
||||
window.parent.postMessage({
|
||||
type: 'videoSelected',
|
||||
embedUrl: embedUrl,
|
||||
videoId: mediaId
|
||||
}, '*');
|
||||
|
||||
console.log('Sent postMessage to parent:', { embedUrl, videoId: mediaId });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Normal mode: allow multiple selection
|
||||
newSelectedMedia.clear();
|
||||
prevState.selectedMedia.forEach((id) => newSelectedMedia.add(id));
|
||||
|
||||
if (isSelected) {
|
||||
newSelectedMedia.add(mediaId);
|
||||
} else {
|
||||
newSelectedMedia.delete(mediaId);
|
||||
window.parent.postMessage({
|
||||
type: 'videoSelected',
|
||||
embedUrl: embedUrl,
|
||||
videoId: mediaId
|
||||
}, '*');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,386 +173,6 @@ export class ProfileMediaPage extends Page {
|
||||
});
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -651,7 +199,6 @@ export class ProfileMediaPage extends Page {
|
||||
|
||||
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]
|
||||
@@ -673,7 +220,6 @@ export class ProfileMediaPage extends Page {
|
||||
|
||||
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]
|
||||
@@ -722,19 +268,16 @@ export class ProfileMediaPage extends Page {
|
||||
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';
|
||||
@@ -759,7 +302,6 @@ export class ProfileMediaPage extends Page {
|
||||
break;
|
||||
}
|
||||
|
||||
// Handle tag filter
|
||||
if (updatedArgs.tag && updatedArgs.tag !== 'all') {
|
||||
args.t = updatedArgs.tag;
|
||||
}
|
||||
@@ -775,10 +317,8 @@ export class ProfileMediaPage extends Page {
|
||||
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;
|
||||
}
|
||||
@@ -805,137 +345,7 @@ export class ProfileMediaPage extends Page {
|
||||
);
|
||||
}
|
||||
|
||||
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(',')
|
||||
@@ -951,7 +361,6 @@ export class ProfileMediaPage extends Page {
|
||||
const isMediaAuthor = authorData && authorData.username === MemberContext._currentValue.username;
|
||||
const isSelectMediaMode = inSelectMediaEmbedMode();
|
||||
|
||||
// Check if any filters are active (excluding default sort and tags)
|
||||
const hasActiveFilters =
|
||||
this.state.filterArgs &&
|
||||
(this.state.filterArgs.includes('media_type=') ||
|
||||
@@ -985,11 +394,11 @@ export class ProfileMediaPage extends Page {
|
||||
className="items-list-ver"
|
||||
style={inEmbeddedApp() ? { marginTop: '24px' } : undefined}
|
||||
showBulkActions={!isSelectMediaMode && isMediaAuthor}
|
||||
selectedCount={this.state.selectedMedia.size}
|
||||
totalCount={this.state.availableMediaIds.length}
|
||||
onBulkAction={this.handleBulkAction}
|
||||
onSelectAll={this.handleSelectAll}
|
||||
onDeselectAll={this.handleDeselectAll}
|
||||
selectedCount={isSelectMediaMode ? this.state.selectedMedia.size : this.props.bulkActions.selectedMedia.size}
|
||||
totalCount={isSelectMediaMode ? 0 : this.props.bulkActions.availableMediaIds.length}
|
||||
onBulkAction={this.props.bulkActions.handleBulkAction}
|
||||
onSelectAll={this.props.bulkActions.handleSelectAll}
|
||||
onDeselectAll={this.props.bulkActions.handleDeselectAll}
|
||||
showAddMediaButton={!isSelectMediaMode && isMediaAuthor}
|
||||
>
|
||||
<ProfileMediaFilters
|
||||
@@ -1006,7 +415,7 @@ export class ProfileMediaPage extends Page {
|
||||
/>
|
||||
<ProfileMediaSorting hidden={this.state.hiddenSorting} onSortSelect={this.onSortSelect} />
|
||||
<LazyLoadItemListAsync
|
||||
key={`${this.state.requestUrl}-${this.state.listKey}`}
|
||||
key={`${this.state.requestUrl}-${this.props.bulkActions.listKey}`}
|
||||
requestUrl={this.state.requestUrl}
|
||||
hideAuthor={true}
|
||||
itemsCountCallback={this.state.requestUrl ? this.getCountFunc : null}
|
||||
@@ -1014,97 +423,43 @@ export class ProfileMediaPage extends Page {
|
||||
hideDate={!PageStore.get('config-media-item').displayPublishDate}
|
||||
canEdit={isMediaAuthor}
|
||||
showSelection={isMediaAuthor || isSelectMediaMode}
|
||||
hasAnySelection={this.state.selectedMedia.size > 0}
|
||||
selectedMedia={this.state.selectedMedia}
|
||||
onMediaSelection={this.handleMediaSelection}
|
||||
onItemsUpdate={this.handleItemsUpdate}
|
||||
hasAnySelection={isSelectMediaMode ? this.state.selectedMedia.size > 0 : this.props.bulkActions.selectedMedia.size > 0}
|
||||
selectedMedia={isSelectMediaMode ? this.state.selectedMedia : this.props.bulkActions.selectedMedia}
|
||||
onMediaSelection={isSelectMediaMode ? this.handleMediaSelection : this.props.bulkActions.handleMediaSelection}
|
||||
onItemsUpdate={!isSelectMediaMode ? this.props.bulkActions.handleItemsUpdate : undefined}
|
||||
onResponseDataLoaded={this.onResponseDataLoaded}
|
||||
/>
|
||||
</MediaListWrapper>
|
||||
</ProfilePagesContent>
|
||||
) : null,
|
||||
<BulkActionConfirmModal
|
||||
key="BulkActionConfirmModal"
|
||||
isOpen={this.state.showConfirmModal}
|
||||
message={this.state.confirmMessage}
|
||||
onCancel={this.handleConfirmCancel}
|
||||
onProceed={this.handleConfirmProceed}
|
||||
/>,
|
||||
<BulkActionPermissionModal
|
||||
key="BulkActionPermissionModal"
|
||||
isOpen={this.state.showPermissionModal}
|
||||
permissionType={this.state.permissionType}
|
||||
selectedMediaIds={Array.from(this.state.selectedMedia)}
|
||||
onCancel={this.handlePermissionModalCancel}
|
||||
onSuccess={this.handlePermissionModalSuccess}
|
||||
onError={this.handlePermissionModalError}
|
||||
csrfToken={this.getCsrfToken()}
|
||||
/>,
|
||||
<BulkActionPlaylistModal
|
||||
key="BulkActionPlaylistModal"
|
||||
isOpen={this.state.showPlaylistModal}
|
||||
selectedMediaIds={Array.from(this.state.selectedMedia)}
|
||||
onCancel={this.handlePlaylistModalCancel}
|
||||
onSuccess={this.handlePlaylistModalSuccess}
|
||||
onError={this.handlePlaylistModalError}
|
||||
csrfToken={this.getCsrfToken()}
|
||||
username={this.state.author ? this.state.author.username : ''}
|
||||
/>,
|
||||
<BulkActionChangeOwnerModal
|
||||
key="BulkActionChangeOwnerModal"
|
||||
isOpen={this.state.showChangeOwnerModal}
|
||||
selectedMediaIds={Array.from(this.state.selectedMedia)}
|
||||
onCancel={this.handleChangeOwnerModalCancel}
|
||||
onSuccess={this.handleChangeOwnerModalSuccess}
|
||||
onError={this.handleChangeOwnerModalError}
|
||||
csrfToken={this.getCsrfToken()}
|
||||
/>,
|
||||
<BulkActionPublishStateModal
|
||||
key="BulkActionPublishStateModal"
|
||||
isOpen={this.state.showPublishStateModal}
|
||||
selectedMediaIds={Array.from(this.state.selectedMedia)}
|
||||
onCancel={this.handlePublishStateModalCancel}
|
||||
onSuccess={this.handlePublishStateModalSuccess}
|
||||
onError={this.handlePublishStateModalError}
|
||||
csrfToken={this.getCsrfToken()}
|
||||
/>,
|
||||
<BulkActionCategoryModal
|
||||
key="BulkActionCategoryModal"
|
||||
isOpen={this.state.showCategoryModal}
|
||||
selectedMediaIds={Array.from(this.state.selectedMedia)}
|
||||
onCancel={this.handleCategoryModalCancel}
|
||||
onSuccess={this.handleCategoryModalSuccess}
|
||||
onError={this.handleCategoryModalError}
|
||||
csrfToken={this.getCsrfToken()}
|
||||
/>,
|
||||
<BulkActionTagModal
|
||||
key="BulkActionTagModal"
|
||||
isOpen={this.state.showTagModal}
|
||||
selectedMediaIds={Array.from(this.state.selectedMedia)}
|
||||
onCancel={this.handleTagModalCancel}
|
||||
onSuccess={this.handleTagModalSuccess}
|
||||
onError={this.handleTagModalError}
|
||||
csrfToken={this.getCsrfToken()}
|
||||
/>,
|
||||
this.state.showNotification ? (
|
||||
<div
|
||||
key="SimpleNotification"
|
||||
style={{
|
||||
position: 'fixed',
|
||||
bottom: '20px',
|
||||
left: '260px',
|
||||
backgroundColor: this.state.notificationType === 'error' ? '#f44336' : '#4CAF50',
|
||||
color: 'white',
|
||||
padding: '16px 24px',
|
||||
borderRadius: '4px',
|
||||
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
|
||||
zIndex: 1000,
|
||||
fontSize: '14px',
|
||||
fontWeight: '500',
|
||||
}}
|
||||
>
|
||||
{this.state.notificationMessage}
|
||||
</div>
|
||||
this.state.author && isMediaAuthor && !isSelectMediaMode ? (
|
||||
<BulkActionsModals
|
||||
key="BulkActionsModals"
|
||||
{...this.props.bulkActions}
|
||||
selectedMediaIds={Array.from(this.props.bulkActions.selectedMedia)}
|
||||
csrfToken={this.props.bulkActions.getCsrfToken()}
|
||||
username={this.state.author.username}
|
||||
onConfirmCancel={this.props.bulkActions.handleConfirmCancel}
|
||||
onConfirmProceed={this.props.bulkActions.handleConfirmProceed}
|
||||
onPermissionModalCancel={this.props.bulkActions.handlePermissionModalCancel}
|
||||
onPermissionModalSuccess={this.props.bulkActions.handlePermissionModalSuccess}
|
||||
onPermissionModalError={this.props.bulkActions.handlePermissionModalError}
|
||||
onPlaylistModalCancel={this.props.bulkActions.handlePlaylistModalCancel}
|
||||
onPlaylistModalSuccess={this.props.bulkActions.handlePlaylistModalSuccess}
|
||||
onPlaylistModalError={this.props.bulkActions.handlePlaylistModalError}
|
||||
onChangeOwnerModalCancel={this.props.bulkActions.handleChangeOwnerModalCancel}
|
||||
onChangeOwnerModalSuccess={this.props.bulkActions.handleChangeOwnerModalSuccess}
|
||||
onChangeOwnerModalError={this.props.bulkActions.handleChangeOwnerModalError}
|
||||
onPublishStateModalCancel={this.props.bulkActions.handlePublishStateModalCancel}
|
||||
onPublishStateModalSuccess={this.props.bulkActions.handlePublishStateModalSuccess}
|
||||
onPublishStateModalError={this.props.bulkActions.handlePublishStateModalError}
|
||||
onCategoryModalCancel={this.props.bulkActions.handleCategoryModalCancel}
|
||||
onCategoryModalSuccess={this.props.bulkActions.handleCategoryModalSuccess}
|
||||
onCategoryModalError={this.props.bulkActions.handleCategoryModalError}
|
||||
onTagModalCancel={this.props.bulkActions.handleTagModalCancel}
|
||||
onTagModalSuccess={this.props.bulkActions.handleTagModalSuccess}
|
||||
onTagModalError={this.props.bulkActions.handleTagModalError}
|
||||
/>
|
||||
) : null,
|
||||
];
|
||||
}
|
||||
@@ -1112,8 +467,17 @@ export class ProfileMediaPage extends Page {
|
||||
|
||||
ProfileMediaPage.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
bulkActions: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
ProfileMediaPage.defaultProps = {
|
||||
title: 'Uploads',
|
||||
};
|
||||
|
||||
// Export the raw class for subclasses to extend
|
||||
export { ProfileMediaPage as ProfileMediaPageBase };
|
||||
|
||||
// Export the HOC-wrapped version as the renderable component
|
||||
const WrappedProfileMediaPage = withBulkActions(ProfileMediaPage);
|
||||
export { WrappedProfileMediaPage as ProfileMediaPage };
|
||||
export default WrappedProfileMediaPage;
|
||||
|
||||
@@ -6,9 +6,9 @@ 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.jsx';
|
||||
import { ProfileMediaPage } from './ProfileMediaPage';
|
||||
import { ProfileMediaPageBase } from './ProfileMediaPage';
|
||||
|
||||
export class ProfilePlaylistsPage extends ProfileMediaPage {
|
||||
export class ProfilePlaylistsPage extends ProfileMediaPageBase {
|
||||
constructor(props) {
|
||||
super(props, 'author-playlists');
|
||||
|
||||
|
||||
@@ -111,6 +111,10 @@ export function useBulkActions() {
|
||||
setShowConfirmModal(true);
|
||||
setPendingAction(action);
|
||||
setConfirmMessage(translateString('You are going to disable comments to') + ` ${selectedCount} ` + translateString('media, are you sure?'));
|
||||
} else if (action === 'delete-comments') {
|
||||
setShowConfirmModal(true);
|
||||
setPendingAction(action);
|
||||
setConfirmMessage(translateString('You are going to delete all comments from') + ` ${selectedCount} ` + translateString('media, are you sure?'));
|
||||
} else if (action === 'enable-download') {
|
||||
setShowConfirmModal(true);
|
||||
setPendingAction(action);
|
||||
@@ -165,6 +169,8 @@ export function useBulkActions() {
|
||||
executeEnableComments();
|
||||
} else if (action === 'disable-comments') {
|
||||
executeDisableComments();
|
||||
} else if (action === 'delete-comments') {
|
||||
executeDeleteComments();
|
||||
} else if (action === 'enable-download') {
|
||||
executeEnableDownload();
|
||||
} else if (action === 'disable-download') {
|
||||
@@ -271,6 +277,37 @@ export function useBulkActions() {
|
||||
});
|
||||
};
|
||||
|
||||
// Execute delete comments
|
||||
const executeDeleteComments = () => {
|
||||
const selectedIds = Array.from(selectedMedia);
|
||||
|
||||
fetch('/api/v1/media/user/bulk_actions', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': getCsrfToken(),
|
||||
},
|
||||
body: JSON.stringify({
|
||||
action: 'delete_comments',
|
||||
media_ids: selectedIds,
|
||||
}),
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to delete comments');
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then((data) => {
|
||||
showNotificationMessage(translateString('Successfully deleted comments'));
|
||||
clearSelection();
|
||||
})
|
||||
.catch((error) => {
|
||||
showNotificationMessage(translateString('Failed to delete comments.'), 'error');
|
||||
clearSelection();
|
||||
});
|
||||
};
|
||||
|
||||
// Execute enable download
|
||||
const executeEnableDownload = () => {
|
||||
const selectedIds = Array.from(selectedMedia);
|
||||
|
||||
Reference in New Issue
Block a user