mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-11-05 23:18:53 -05:00
Compare commits
9 Commits
02eb712a50
...
e0f8e839cf
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e0f8e839cf | ||
|
|
42d65ed4e4 | ||
|
|
14de46f8d3 | ||
|
|
29d939c47c | ||
|
|
9ccd0fa44e | ||
|
|
7fa605ff6b | ||
|
|
255d004ecb | ||
|
|
f701872d39 | ||
|
|
891676243e |
@ -4,6 +4,8 @@ import tempfile
|
||||
import pysubs2
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
from django.urls import reverse
|
||||
|
||||
from .. import helpers
|
||||
@ -100,3 +102,13 @@ class TranscriptionRequest(models.Model):
|
||||
|
||||
def __str__(self):
|
||||
return f"Transcription request for {self.media.title} - {self.status}"
|
||||
|
||||
|
||||
@receiver(post_save, sender=Subtitle)
|
||||
def subtitle_save(sender, instance, created, **kwargs):
|
||||
from .. import tasks
|
||||
|
||||
tasks.update_search_vector.apply_async(
|
||||
args=[instance.media.friendly_token],
|
||||
countdown=10,
|
||||
)
|
||||
|
||||
@ -528,6 +528,17 @@ def whisper_transcribe(friendly_token, translate_to_english=False):
|
||||
return False
|
||||
|
||||
|
||||
@task(name="update_search_vector", queue="short_tasks")
|
||||
def update_search_vector(friendly_token):
|
||||
try:
|
||||
media = Media.objects.get(friendly_token=friendly_token)
|
||||
media.update_search_vector()
|
||||
except: # noqa
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@task(name="produce_sprite_from_video", queue="long_tasks")
|
||||
def produce_sprite_from_video(friendly_token):
|
||||
"""Produces a sprites file for a video, uses ffmpeg"""
|
||||
|
||||
@ -96,7 +96,7 @@ class MediaList(APIView):
|
||||
rbac_conditions &= Q(user=user)
|
||||
conditions |= rbac_conditions
|
||||
|
||||
return base_queryset.filter(conditions).distinct()[:1000]
|
||||
return base_queryset.filter(conditions).distinct()
|
||||
|
||||
def get(self, request, format=None):
|
||||
# Show media
|
||||
@ -116,6 +116,7 @@ class MediaList(APIView):
|
||||
upload_date = params.get('upload_date', '').strip()
|
||||
duration = params.get('duration', '').strip()
|
||||
publish_state = params.get('publish_state', '').strip()
|
||||
query = params.get("q", "").strip().lower()
|
||||
|
||||
# Handle combined sort options (e.g., title_asc, views_desc)
|
||||
parsed_combined = False
|
||||
@ -168,7 +169,7 @@ class MediaList(APIView):
|
||||
if not self.request.user.is_authenticated:
|
||||
media = Media.objects.none()
|
||||
else:
|
||||
media = Media.objects.filter(permissions__owner_user=self.request.user).prefetch_related("user", "tags")
|
||||
media = Media.objects.filter(permissions__owner_user=self.request.user).prefetch_related("user", "tags").distinct()
|
||||
elif show_param == "shared_with_me":
|
||||
if not self.request.user.is_authenticated:
|
||||
media = Media.objects.none()
|
||||
@ -221,11 +222,13 @@ class MediaList(APIView):
|
||||
if publish_state and publish_state in ['private', 'public', 'unlisted']:
|
||||
media = media.filter(state=publish_state)
|
||||
|
||||
if query:
|
||||
media = media.filter(title__icontains=query)
|
||||
|
||||
if not already_sorted:
|
||||
media = media.order_by(f"{ordering}{sort_by}")
|
||||
|
||||
if show_param == "shared_with_me":
|
||||
media = media[:1000] # limit to 1000 results
|
||||
media = media[:1000] # limit to 1000 results
|
||||
|
||||
paginator = pagination_class()
|
||||
|
||||
|
||||
@ -232,7 +232,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.playlist-item {
|
||||
.playlist-list .playlist-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
201
frontend/src/static/js/components/BulkActionsModals.jsx
Normal file
201
frontend/src/static/js/components/BulkActionsModals.jsx
Normal file
@ -0,0 +1,201 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { BulkActionConfirmModal } from './BulkActionConfirmModal';
|
||||
import { BulkActionPermissionModal } from './BulkActionPermissionModal';
|
||||
import { BulkActionPlaylistModal } from './BulkActionPlaylistModal';
|
||||
import { BulkActionChangeOwnerModal } from './BulkActionChangeOwnerModal';
|
||||
import { BulkActionPublishStateModal } from './BulkActionPublishStateModal';
|
||||
import { BulkActionCategoryModal } from './BulkActionCategoryModal';
|
||||
import { BulkActionTagModal } from './BulkActionTagModal';
|
||||
|
||||
/**
|
||||
* Renders all bulk action modals
|
||||
* This component is reusable across different pages
|
||||
*/
|
||||
export function BulkActionsModals({
|
||||
// Confirm modal props
|
||||
showConfirmModal,
|
||||
confirmMessage,
|
||||
onConfirmCancel,
|
||||
onConfirmProceed,
|
||||
|
||||
// Permission modal props
|
||||
showPermissionModal,
|
||||
permissionType,
|
||||
selectedMediaIds,
|
||||
onPermissionModalCancel,
|
||||
onPermissionModalSuccess,
|
||||
onPermissionModalError,
|
||||
|
||||
// Playlist modal props
|
||||
showPlaylistModal,
|
||||
onPlaylistModalCancel,
|
||||
onPlaylistModalSuccess,
|
||||
onPlaylistModalError,
|
||||
username,
|
||||
|
||||
// Change owner modal props
|
||||
showChangeOwnerModal,
|
||||
onChangeOwnerModalCancel,
|
||||
onChangeOwnerModalSuccess,
|
||||
onChangeOwnerModalError,
|
||||
|
||||
// Publish state modal props
|
||||
showPublishStateModal,
|
||||
onPublishStateModalCancel,
|
||||
onPublishStateModalSuccess,
|
||||
onPublishStateModalError,
|
||||
|
||||
// Category modal props
|
||||
showCategoryModal,
|
||||
onCategoryModalCancel,
|
||||
onCategoryModalSuccess,
|
||||
onCategoryModalError,
|
||||
|
||||
// Tag modal props
|
||||
showTagModal,
|
||||
onTagModalCancel,
|
||||
onTagModalSuccess,
|
||||
onTagModalError,
|
||||
|
||||
// Common props
|
||||
csrfToken,
|
||||
|
||||
// Notification
|
||||
showNotification,
|
||||
notificationMessage,
|
||||
notificationType,
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<BulkActionConfirmModal
|
||||
isOpen={showConfirmModal}
|
||||
message={confirmMessage}
|
||||
onCancel={onConfirmCancel}
|
||||
onProceed={onConfirmProceed}
|
||||
/>
|
||||
|
||||
<BulkActionPermissionModal
|
||||
isOpen={showPermissionModal}
|
||||
permissionType={permissionType}
|
||||
selectedMediaIds={selectedMediaIds}
|
||||
onCancel={onPermissionModalCancel}
|
||||
onSuccess={onPermissionModalSuccess}
|
||||
onError={onPermissionModalError}
|
||||
csrfToken={csrfToken}
|
||||
/>
|
||||
|
||||
<BulkActionPlaylistModal
|
||||
isOpen={showPlaylistModal}
|
||||
selectedMediaIds={selectedMediaIds}
|
||||
onCancel={onPlaylistModalCancel}
|
||||
onSuccess={onPlaylistModalSuccess}
|
||||
onError={onPlaylistModalError}
|
||||
csrfToken={csrfToken}
|
||||
username={username}
|
||||
/>
|
||||
|
||||
<BulkActionChangeOwnerModal
|
||||
isOpen={showChangeOwnerModal}
|
||||
selectedMediaIds={selectedMediaIds}
|
||||
onCancel={onChangeOwnerModalCancel}
|
||||
onSuccess={onChangeOwnerModalSuccess}
|
||||
onError={onChangeOwnerModalError}
|
||||
csrfToken={csrfToken}
|
||||
/>
|
||||
|
||||
<BulkActionPublishStateModal
|
||||
isOpen={showPublishStateModal}
|
||||
selectedMediaIds={selectedMediaIds}
|
||||
onCancel={onPublishStateModalCancel}
|
||||
onSuccess={onPublishStateModalSuccess}
|
||||
onError={onPublishStateModalError}
|
||||
csrfToken={csrfToken}
|
||||
/>
|
||||
|
||||
<BulkActionCategoryModal
|
||||
isOpen={showCategoryModal}
|
||||
selectedMediaIds={selectedMediaIds}
|
||||
onCancel={onCategoryModalCancel}
|
||||
onSuccess={onCategoryModalSuccess}
|
||||
onError={onCategoryModalError}
|
||||
csrfToken={csrfToken}
|
||||
/>
|
||||
|
||||
<BulkActionTagModal
|
||||
isOpen={showTagModal}
|
||||
selectedMediaIds={selectedMediaIds}
|
||||
onCancel={onTagModalCancel}
|
||||
onSuccess={onTagModalSuccess}
|
||||
onError={onTagModalError}
|
||||
csrfToken={csrfToken}
|
||||
/>
|
||||
|
||||
{showNotification && (
|
||||
<div
|
||||
style={{
|
||||
position: 'fixed',
|
||||
bottom: '20px',
|
||||
left: '260px',
|
||||
backgroundColor: 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',
|
||||
}}
|
||||
>
|
||||
{notificationMessage}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
BulkActionsModals.propTypes = {
|
||||
showConfirmModal: PropTypes.bool.isRequired,
|
||||
confirmMessage: PropTypes.string.isRequired,
|
||||
onConfirmCancel: PropTypes.func.isRequired,
|
||||
onConfirmProceed: PropTypes.func.isRequired,
|
||||
|
||||
showPermissionModal: PropTypes.bool.isRequired,
|
||||
permissionType: PropTypes.oneOf(['viewer', 'editor', 'owner', null]),
|
||||
selectedMediaIds: PropTypes.array.isRequired,
|
||||
onPermissionModalCancel: PropTypes.func.isRequired,
|
||||
onPermissionModalSuccess: PropTypes.func.isRequired,
|
||||
onPermissionModalError: PropTypes.func.isRequired,
|
||||
|
||||
showPlaylistModal: PropTypes.bool.isRequired,
|
||||
onPlaylistModalCancel: PropTypes.func.isRequired,
|
||||
onPlaylistModalSuccess: PropTypes.func.isRequired,
|
||||
onPlaylistModalError: PropTypes.func.isRequired,
|
||||
username: PropTypes.string,
|
||||
|
||||
showChangeOwnerModal: PropTypes.bool.isRequired,
|
||||
onChangeOwnerModalCancel: PropTypes.func.isRequired,
|
||||
onChangeOwnerModalSuccess: PropTypes.func.isRequired,
|
||||
onChangeOwnerModalError: PropTypes.func.isRequired,
|
||||
|
||||
showPublishStateModal: PropTypes.bool.isRequired,
|
||||
onPublishStateModalCancel: PropTypes.func.isRequired,
|
||||
onPublishStateModalSuccess: PropTypes.func.isRequired,
|
||||
onPublishStateModalError: PropTypes.func.isRequired,
|
||||
|
||||
showCategoryModal: PropTypes.bool.isRequired,
|
||||
onCategoryModalCancel: PropTypes.func.isRequired,
|
||||
onCategoryModalSuccess: PropTypes.func.isRequired,
|
||||
onCategoryModalError: PropTypes.func.isRequired,
|
||||
|
||||
showTagModal: PropTypes.bool.isRequired,
|
||||
onTagModalCancel: PropTypes.func.isRequired,
|
||||
onTagModalSuccess: PropTypes.func.isRequired,
|
||||
onTagModalError: PropTypes.func.isRequired,
|
||||
|
||||
csrfToken: PropTypes.string.isRequired,
|
||||
|
||||
showNotification: PropTypes.bool.isRequired,
|
||||
notificationMessage: PropTypes.string.isRequired,
|
||||
notificationType: PropTypes.oneOf(['success', 'error']).isRequired,
|
||||
};
|
||||
@ -45,10 +45,11 @@ class ProfileSearchBar extends React.PureComponent {
|
||||
|
||||
onChange(ev) {
|
||||
this.pendingEvent = ev;
|
||||
const newValue = ev.target.value || '';
|
||||
|
||||
this.setState(
|
||||
{
|
||||
queryVal: ev.target.value || '',
|
||||
queryVal: newValue,
|
||||
},
|
||||
function () {
|
||||
if (this.updateTimeout) {
|
||||
@ -57,8 +58,11 @@ class ProfileSearchBar extends React.PureComponent {
|
||||
|
||||
this.pendingEvent = null;
|
||||
|
||||
// Only trigger search if 3+ characters or empty (to reset)
|
||||
if ('function' === typeof this.props.onQueryChange) {
|
||||
this.props.onQueryChange(this.state.queryVal);
|
||||
if (newValue.length >= 3 || newValue.length === 0) {
|
||||
this.props.onQueryChange(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
this.updateTimeout = setTimeout(
|
||||
@ -137,24 +141,56 @@ class ProfileSearchBar extends React.PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const hasSearchText = this.state.queryVal && this.state.queryVal.length > 0;
|
||||
|
||||
// Determine the correct action URL based on page type
|
||||
let actionUrl = LinksContext._currentValue.profile.media;
|
||||
if (this.props.type === 'shared_by_me') {
|
||||
actionUrl = LinksContext._currentValue.profile.shared_by_me;
|
||||
} else if (this.props.type === 'shared_with_me') {
|
||||
actionUrl = LinksContext._currentValue.profile.shared_with_me;
|
||||
}
|
||||
|
||||
if (!this.state.visibleForm) {
|
||||
return (
|
||||
<div>
|
||||
<span>
|
||||
<CircleIconButton buttonShadow={false} onClick={this.showForm}>
|
||||
<i className="material-icons">search</i>
|
||||
</CircleIconButton>
|
||||
</span>
|
||||
</div>
|
||||
<span style={{ display: 'flex', alignItems: 'center', cursor: 'pointer', position: 'relative' }} onClick={this.showForm}>
|
||||
<CircleIconButton buttonShadow={false}>
|
||||
<i className="material-icons">search</i>
|
||||
</CircleIconButton>
|
||||
{hasSearchText ? (
|
||||
<span style={{
|
||||
position: 'absolute',
|
||||
top: '8px',
|
||||
right: '8px',
|
||||
width: '8px',
|
||||
height: '8px',
|
||||
borderRadius: '50%',
|
||||
backgroundColor: 'var(--default-theme-color)',
|
||||
border: '2px solid white',
|
||||
}}></span>
|
||||
) : null}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<form method="get" action={LinksContext._currentValue.profile.media} onSubmit={this.onFormSubmit}>
|
||||
<span>
|
||||
<form method="get" action={actionUrl} onSubmit={this.onFormSubmit}>
|
||||
<span style={{ display: 'flex', alignItems: 'center', position: 'relative' }}>
|
||||
<CircleIconButton buttonShadow={false}>
|
||||
<i className="material-icons">search</i>
|
||||
</CircleIconButton>
|
||||
{hasSearchText ? (
|
||||
<span style={{
|
||||
position: 'absolute',
|
||||
top: '8px',
|
||||
right: '8px',
|
||||
width: '8px',
|
||||
height: '8px',
|
||||
borderRadius: '50%',
|
||||
backgroundColor: 'var(--default-theme-color)',
|
||||
border: '2px solid white',
|
||||
}}></span>
|
||||
) : null}
|
||||
</span>
|
||||
<span>
|
||||
<input
|
||||
@ -177,6 +213,7 @@ class ProfileSearchBar extends React.PureComponent {
|
||||
|
||||
ProfileSearchBar.propTypes = {
|
||||
onQueryChange: PropTypes.func,
|
||||
type: PropTypes.string,
|
||||
};
|
||||
|
||||
ProfileSearchBar.defaultProps = {};
|
||||
@ -368,7 +405,7 @@ class NavMenuInlineTabs extends React.PureComponent {
|
||||
) : null}
|
||||
|
||||
<li className="media-search">
|
||||
<ProfileSearchBar onQueryChange={this.props.onQueryChange} toggleSearchField={this.onToggleSearchField} />
|
||||
<ProfileSearchBar onQueryChange={this.props.onQueryChange} toggleSearchField={this.onToggleSearchField} type={this.props.type} />
|
||||
</li>
|
||||
{this.props.onToggleFiltersClick && ['media', 'shared_by_me', 'shared_with_me'].includes(this.props.type) ? (
|
||||
<li className="media-filters-toggle">
|
||||
|
||||
@ -23,24 +23,6 @@ import { Page } from './_Page';
|
||||
|
||||
import '../components/profile-page/ProfilePage.scss';
|
||||
|
||||
function EmptyChannelMedia(props) {
|
||||
return (
|
||||
<LinksConsumer>
|
||||
{(links) => (
|
||||
<div className="empty-media empty-channel-media">
|
||||
<div className="welcome-title">{translateString('Welcome')} {props.name}</div>
|
||||
<div className="start-uploading">
|
||||
{translateString('Start uploading media and sharing your work. Media that you upload will show up here.')}
|
||||
</div>
|
||||
<a href={links.user.addMedia} title={translateString('Upload media')} className="button-link">
|
||||
<i className="material-icons" data-icon="video_call"></i>{translateString('UPLOAD MEDIA')}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</LinksConsumer>
|
||||
);
|
||||
}
|
||||
|
||||
export class ProfileMediaPage extends Page {
|
||||
constructor(props, pageSlug) {
|
||||
super(props, 'string' === typeof pageSlug ? pageSlug : 'author-home');
|
||||
@ -138,7 +120,7 @@ export class ProfileMediaPage extends Page {
|
||||
|
||||
if (author) {
|
||||
if (this.state.query) {
|
||||
requestUrl = ApiUrlContext._currentValue.search.query + this.state.query + '&author=' + author.id + this.state.filterArgs;
|
||||
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;
|
||||
}
|
||||
@ -189,7 +171,7 @@ export class ProfileMediaPage extends Page {
|
||||
let requestUrl;
|
||||
|
||||
if (newQuery) {
|
||||
requestUrl = ApiUrlContext._currentValue.search.query + newQuery + '&author=' + this.state.author.id + this.state.filterArgs;
|
||||
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;
|
||||
}
|
||||
@ -721,7 +703,7 @@ export class ProfileMediaPage extends Page {
|
||||
let requestUrl;
|
||||
|
||||
if (this.state.query) {
|
||||
requestUrl = ApiUrlContext._currentValue.search.query + this.state.query + '&author=' + this.state.author.id + this.state.filterArgs;
|
||||
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;
|
||||
}
|
||||
@ -940,9 +922,6 @@ export class ProfileMediaPage extends Page {
|
||||
onItemsUpdate={this.handleItemsUpdate}
|
||||
onResponseDataLoaded={this.onResponseDataLoaded}
|
||||
/>
|
||||
{isMediaAuthor && 0 === this.state.channelMediaCount && !this.state.query ? (
|
||||
<EmptyChannelMedia name={this.state.author.name} />
|
||||
) : null}
|
||||
</MediaListWrapper>
|
||||
</ProfilePagesContent>
|
||||
) : null,
|
||||
|
||||
@ -10,7 +10,9 @@ import { LazyLoadItemListAsync } from '../components/item-list/LazyLoadItemListA
|
||||
import { ProfileMediaFilters } from '../components/search-filters/ProfileMediaFilters';
|
||||
import { ProfileMediaTags } from '../components/search-filters/ProfileMediaTags';
|
||||
import { ProfileMediaSorting } from '../components/search-filters/ProfileMediaSorting';
|
||||
import { BulkActionsModals } from '../components/BulkActionsModals';
|
||||
import { translateString } from '../utils/helpers';
|
||||
import { withBulkActions } from '../utils/hoc/withBulkActions';
|
||||
|
||||
import { Page } from './_Page';
|
||||
|
||||
@ -31,7 +33,7 @@ function EmptySharedByMe(props) {
|
||||
);
|
||||
}
|
||||
|
||||
export class ProfileSharedByMePage extends Page {
|
||||
class ProfileSharedByMePage extends Page {
|
||||
constructor(props, pageSlug) {
|
||||
super(props, 'string' === typeof pageSlug ? pageSlug : 'author-shared-by-me');
|
||||
|
||||
@ -79,7 +81,7 @@ export class ProfileSharedByMePage extends Page {
|
||||
|
||||
if (author) {
|
||||
if (this.state.query) {
|
||||
requestUrl = ApiUrlContext._currentValue.search.query + this.state.query + '&author=' + author.id + '&show=shared_by_me' + this.state.filterArgs;
|
||||
requestUrl = ApiUrlContext._currentValue.media + '?author=' + author.id + '&show=shared_by_me&q=' + encodeURIComponent(this.state.query) + this.state.filterArgs;
|
||||
} else {
|
||||
requestUrl = ApiUrlContext._currentValue.media + '?author=' + author.id + '&show=shared_by_me' + this.state.filterArgs;
|
||||
}
|
||||
@ -130,7 +132,7 @@ export class ProfileSharedByMePage extends Page {
|
||||
let requestUrl;
|
||||
|
||||
if (newQuery) {
|
||||
requestUrl = ApiUrlContext._currentValue.search.query + newQuery + '&author=' + this.state.author.id + '&show=shared_by_me' + this.state.filterArgs;
|
||||
requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + '&show=shared_by_me&q=' + encodeURIComponent(newQuery) + this.state.filterArgs;
|
||||
} else {
|
||||
requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + '&show=shared_by_me' + this.state.filterArgs;
|
||||
}
|
||||
@ -272,7 +274,7 @@ export class ProfileSharedByMePage extends Page {
|
||||
let requestUrl;
|
||||
|
||||
if (this.state.query) {
|
||||
requestUrl = ApiUrlContext._currentValue.search.query + this.state.query + '&author=' + this.state.author.id + '&show=shared_by_me' + this.state.filterArgs;
|
||||
requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + '&show=shared_by_me&q=' + encodeURIComponent(this.state.query) + this.state.filterArgs;
|
||||
} else {
|
||||
requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + '&show=shared_by_me' + this.state.filterArgs;
|
||||
}
|
||||
@ -320,14 +322,20 @@ export class ProfileSharedByMePage extends Page {
|
||||
this.state.author ? (
|
||||
<ProfilePagesContent key="ProfilePagesContent">
|
||||
<MediaListWrapper
|
||||
title={!isMediaAuthor || 0 < this.state.channelMediaCount ? this.state.title : null}
|
||||
title={this.state.title}
|
||||
className="items-list-ver"
|
||||
showBulkActions={isMediaAuthor}
|
||||
selectedCount={this.props.bulkActions.selectedMedia.size}
|
||||
totalCount={this.props.bulkActions.availableMediaIds.length}
|
||||
onBulkAction={this.props.bulkActions.handleBulkAction}
|
||||
onSelectAll={this.props.bulkActions.handleSelectAll}
|
||||
onDeselectAll={this.props.bulkActions.handleDeselectAll}
|
||||
>
|
||||
<ProfileMediaFilters hidden={this.state.hiddenFilters} tags={this.state.availableTags} onFiltersUpdate={this.onFiltersUpdate} />
|
||||
<ProfileMediaTags hidden={this.state.hiddenTags} tags={this.state.availableTags} onTagSelect={this.onTagSelect} />
|
||||
<ProfileMediaSorting hidden={this.state.hiddenSorting} onSortSelect={this.onSortSelect} />
|
||||
<LazyLoadItemListAsync
|
||||
key={this.state.requestUrl}
|
||||
key={`${this.state.requestUrl}-${this.props.bulkActions.listKey}`}
|
||||
requestUrl={this.state.requestUrl}
|
||||
hideAuthor={true}
|
||||
itemsCountCallback={this.state.requestUrl ? this.getCountFunc : null}
|
||||
@ -335,6 +343,11 @@ export class ProfileSharedByMePage extends Page {
|
||||
hideDate={!PageStore.get('config-media-item').displayPublishDate}
|
||||
canEdit={false}
|
||||
onResponseDataLoaded={this.onResponseDataLoaded}
|
||||
showSelection={isMediaAuthor}
|
||||
hasAnySelection={this.props.bulkActions.selectedMedia.size > 0}
|
||||
selectedMedia={this.props.bulkActions.selectedMedia}
|
||||
onMediaSelection={this.props.bulkActions.handleMediaSelection}
|
||||
onItemsUpdate={this.props.bulkActions.handleItemsUpdate}
|
||||
/>
|
||||
{isMediaAuthor && 0 === this.state.channelMediaCount && !this.state.query ? (
|
||||
<EmptySharedByMe name={this.state.author.name} />
|
||||
@ -342,14 +355,51 @@ export class ProfileSharedByMePage extends Page {
|
||||
</MediaListWrapper>
|
||||
</ProfilePagesContent>
|
||||
) : null,
|
||||
this.state.author && isMediaAuthor ? (
|
||||
<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,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
ProfileSharedByMePage.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
bulkActions: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
ProfileSharedByMePage.defaultProps = {
|
||||
title: 'Shared by me',
|
||||
};
|
||||
|
||||
// Wrap with HOC and export as named export for compatibility
|
||||
const WrappedProfileSharedByMePage = withBulkActions(ProfileSharedByMePage);
|
||||
|
||||
// Export both the wrapped component as named export (for build system) and default export
|
||||
export { WrappedProfileSharedByMePage as ProfileSharedByMePage };
|
||||
export default WrappedProfileSharedByMePage;
|
||||
|
||||
@ -79,7 +79,7 @@ export class ProfileSharedWithMePage extends Page {
|
||||
|
||||
if (author) {
|
||||
if (this.state.query) {
|
||||
requestUrl = ApiUrlContext._currentValue.search.query + this.state.query + '&author=' + author.id + '&show=shared_with_me' + this.state.filterArgs;
|
||||
requestUrl = ApiUrlContext._currentValue.media + '?author=' + author.id + '&show=shared_with_me&q=' + encodeURIComponent(this.state.query) + this.state.filterArgs;
|
||||
} else {
|
||||
requestUrl = ApiUrlContext._currentValue.media + '?author=' + author.id + '&show=shared_with_me' + this.state.filterArgs;
|
||||
}
|
||||
@ -130,7 +130,7 @@ export class ProfileSharedWithMePage extends Page {
|
||||
let requestUrl;
|
||||
|
||||
if (newQuery) {
|
||||
requestUrl = ApiUrlContext._currentValue.search.query + newQuery + '&author=' + this.state.author.id + '&show=shared_with_me' + this.state.filterArgs;
|
||||
requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + '&show=shared_with_me&q=' + encodeURIComponent(newQuery) + this.state.filterArgs;
|
||||
} else {
|
||||
requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + '&show=shared_with_me' + this.state.filterArgs;
|
||||
}
|
||||
@ -272,7 +272,7 @@ export class ProfileSharedWithMePage extends Page {
|
||||
let requestUrl;
|
||||
|
||||
if (this.state.query) {
|
||||
requestUrl = ApiUrlContext._currentValue.search.query + this.state.query + '&author=' + this.state.author.id + '&show=shared_with_me' + this.state.filterArgs;
|
||||
requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + '&show=shared_with_me&q=' + encodeURIComponent(this.state.query) + this.state.filterArgs;
|
||||
} else {
|
||||
requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + '&show=shared_with_me' + this.state.filterArgs;
|
||||
}
|
||||
@ -320,7 +320,7 @@ export class ProfileSharedWithMePage extends Page {
|
||||
this.state.author ? (
|
||||
<ProfilePagesContent key="ProfilePagesContent">
|
||||
<MediaListWrapper
|
||||
title={!isMediaAuthor || 0 < this.state.channelMediaCount ? this.state.title : null}
|
||||
title={this.state.title}
|
||||
className="items-list-ver"
|
||||
>
|
||||
<ProfileMediaFilters hidden={this.state.hiddenFilters} tags={this.state.availableTags} onFiltersUpdate={this.onFiltersUpdate} />
|
||||
|
||||
@ -9,4 +9,5 @@ export * from './useMediaFilter';
|
||||
export * from './useMediaItem';
|
||||
export * from './usePopup';
|
||||
export * from './useTheme';
|
||||
export * from './useUser';
|
||||
export * from './useUser';
|
||||
export * from './useBulkActions';
|
||||
516
frontend/src/static/js/utils/hooks/useBulkActions.js
Normal file
516
frontend/src/static/js/utils/hooks/useBulkActions.js
Normal file
@ -0,0 +1,516 @@
|
||||
import { useState } from 'react';
|
||||
import { translateString } from '../helpers';
|
||||
|
||||
/**
|
||||
* Custom hook for managing bulk actions on media items
|
||||
* Provides state management and handlers for selecting media and executing bulk actions
|
||||
*/
|
||||
export function useBulkActions() {
|
||||
const [selectedMedia, setSelectedMedia] = useState(new Set());
|
||||
const [availableMediaIds, setAvailableMediaIds] = useState([]);
|
||||
const [showConfirmModal, setShowConfirmModal] = useState(false);
|
||||
const [pendingAction, setPendingAction] = useState(null);
|
||||
const [confirmMessage, setConfirmMessage] = useState('');
|
||||
const [listKey, setListKey] = useState(0);
|
||||
const [notificationMessage, setNotificationMessage] = useState('');
|
||||
const [showNotification, setShowNotification] = useState(false);
|
||||
const [notificationType, setNotificationType] = useState('success');
|
||||
const [showPermissionModal, setShowPermissionModal] = useState(false);
|
||||
const [permissionType, setPermissionType] = useState(null);
|
||||
const [showPlaylistModal, setShowPlaylistModal] = useState(false);
|
||||
const [showChangeOwnerModal, setShowChangeOwnerModal] = useState(false);
|
||||
const [showPublishStateModal, setShowPublishStateModal] = useState(false);
|
||||
const [showCategoryModal, setShowCategoryModal] = useState(false);
|
||||
const [showTagModal, setShowTagModal] = useState(false);
|
||||
|
||||
// Get CSRF token from cookies
|
||||
const 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;
|
||||
};
|
||||
|
||||
// Show notification
|
||||
const showNotificationMessage = (message, type = 'success') => {
|
||||
setNotificationMessage(message);
|
||||
setShowNotification(true);
|
||||
setNotificationType(type);
|
||||
|
||||
setTimeout(() => {
|
||||
setShowNotification(false);
|
||||
}, 5000);
|
||||
};
|
||||
|
||||
// Handle media selection toggle
|
||||
const handleMediaSelection = (mediaId, isSelected) => {
|
||||
setSelectedMedia((prevState) => {
|
||||
const newSelectedMedia = new Set(prevState);
|
||||
if (isSelected) {
|
||||
newSelectedMedia.add(mediaId);
|
||||
} else {
|
||||
newSelectedMedia.delete(mediaId);
|
||||
}
|
||||
return newSelectedMedia;
|
||||
});
|
||||
};
|
||||
|
||||
// Handle items update from list
|
||||
const handleItemsUpdate = (items) => {
|
||||
const mediaIds = items.map((item) => item.friendly_token || item.uid || item.id);
|
||||
setAvailableMediaIds(mediaIds);
|
||||
};
|
||||
|
||||
// Select all available media
|
||||
const handleSelectAll = () => {
|
||||
setSelectedMedia(new Set(availableMediaIds));
|
||||
};
|
||||
|
||||
// Deselect all media
|
||||
const handleDeselectAll = () => {
|
||||
setSelectedMedia(new Set());
|
||||
};
|
||||
|
||||
// Clear selection
|
||||
const clearSelection = () => {
|
||||
setSelectedMedia(new Set());
|
||||
};
|
||||
|
||||
// Clear selection and refresh list
|
||||
const clearSelectionAndRefresh = () => {
|
||||
setSelectedMedia(new Set());
|
||||
setListKey((prev) => prev + 1);
|
||||
};
|
||||
|
||||
// Handle bulk action button clicks
|
||||
const handleBulkAction = (action) => {
|
||||
const selectedCount = selectedMedia.size;
|
||||
|
||||
if (selectedCount === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (action === 'delete-media') {
|
||||
setShowConfirmModal(true);
|
||||
setPendingAction(action);
|
||||
setConfirmMessage(translateString('You are going to delete') + ` ${selectedCount} ` + translateString('media, are you sure?'));
|
||||
} else if (action === 'enable-comments') {
|
||||
setShowConfirmModal(true);
|
||||
setPendingAction(action);
|
||||
setConfirmMessage(translateString('You are going to enable comments to') + ` ${selectedCount} ` + translateString('media, are you sure?'));
|
||||
} else if (action === 'disable-comments') {
|
||||
setShowConfirmModal(true);
|
||||
setPendingAction(action);
|
||||
setConfirmMessage(translateString('You are going to disable comments to') + ` ${selectedCount} ` + translateString('media, are you sure?'));
|
||||
} else if (action === 'enable-download') {
|
||||
setShowConfirmModal(true);
|
||||
setPendingAction(action);
|
||||
setConfirmMessage(translateString('You are going to enable download for') + ` ${selectedCount} ` + translateString('media, are you sure?'));
|
||||
} else if (action === 'disable-download') {
|
||||
setShowConfirmModal(true);
|
||||
setPendingAction(action);
|
||||
setConfirmMessage(translateString('You are going to disable download for') + ` ${selectedCount} ` + translateString('media, are you sure?'));
|
||||
} else if (action === 'copy-media') {
|
||||
setShowConfirmModal(true);
|
||||
setPendingAction(action);
|
||||
setConfirmMessage(translateString('You are going to copy') + ` ${selectedCount} ` + translateString('media, are you sure?'));
|
||||
} else if (action === 'add-remove-coviewers') {
|
||||
setShowPermissionModal(true);
|
||||
setPermissionType('viewer');
|
||||
} else if (action === 'add-remove-coeditors') {
|
||||
setShowPermissionModal(true);
|
||||
setPermissionType('editor');
|
||||
} else if (action === 'add-remove-coowners') {
|
||||
setShowPermissionModal(true);
|
||||
setPermissionType('owner');
|
||||
} else if (action === 'add-remove-playlist') {
|
||||
setShowPlaylistModal(true);
|
||||
} else if (action === 'change-owner') {
|
||||
setShowChangeOwnerModal(true);
|
||||
} else if (action === 'publish-state') {
|
||||
setShowPublishStateModal(true);
|
||||
} else if (action === 'add-remove-category') {
|
||||
setShowCategoryModal(true);
|
||||
} else if (action === 'add-remove-tags') {
|
||||
setShowTagModal(true);
|
||||
}
|
||||
};
|
||||
|
||||
// Cancel confirm modal
|
||||
const handleConfirmCancel = () => {
|
||||
setShowConfirmModal(false);
|
||||
setPendingAction(null);
|
||||
setConfirmMessage('');
|
||||
};
|
||||
|
||||
// Proceed with confirmed action
|
||||
const handleConfirmProceed = () => {
|
||||
const action = pendingAction;
|
||||
setShowConfirmModal(false);
|
||||
setPendingAction(null);
|
||||
setConfirmMessage('');
|
||||
|
||||
if (action === 'delete-media') {
|
||||
executeDeleteMedia();
|
||||
} else if (action === 'enable-comments') {
|
||||
executeEnableComments();
|
||||
} else if (action === 'disable-comments') {
|
||||
executeDisableComments();
|
||||
} else if (action === 'enable-download') {
|
||||
executeEnableDownload();
|
||||
} else if (action === 'disable-download') {
|
||||
executeDisableDownload();
|
||||
} else if (action === 'copy-media') {
|
||||
executeCopyMedia();
|
||||
}
|
||||
};
|
||||
|
||||
// Execute delete media
|
||||
const executeDeleteMedia = () => {
|
||||
const selectedIds = Array.from(selectedMedia);
|
||||
const selectedCount = selectedIds.length;
|
||||
|
||||
fetch('/api/v1/media/user/bulk_actions', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': 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.');
|
||||
showNotificationMessage(message);
|
||||
clearSelectionAndRefresh();
|
||||
})
|
||||
.catch((error) => {
|
||||
showNotificationMessage(translateString('Failed to delete media. Please try again.'), 'error');
|
||||
clearSelectionAndRefresh();
|
||||
});
|
||||
};
|
||||
|
||||
// Execute enable comments
|
||||
const executeEnableComments = () => {
|
||||
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: 'enable_comments',
|
||||
media_ids: selectedIds,
|
||||
}),
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to enable comments');
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then((data) => {
|
||||
showNotificationMessage(translateString('Successfully Enabled comments'));
|
||||
clearSelection();
|
||||
})
|
||||
.catch((error) => {
|
||||
showNotificationMessage(translateString('Failed to enable comments.'), 'error');
|
||||
clearSelection();
|
||||
});
|
||||
};
|
||||
|
||||
// Execute disable comments
|
||||
const executeDisableComments = () => {
|
||||
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: 'disable_comments',
|
||||
media_ids: selectedIds,
|
||||
}),
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to disable comments');
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then((data) => {
|
||||
showNotificationMessage(translateString('Successfully Disabled comments'));
|
||||
clearSelection();
|
||||
})
|
||||
.catch((error) => {
|
||||
showNotificationMessage(translateString('Failed to disable comments.'), 'error');
|
||||
clearSelection();
|
||||
});
|
||||
};
|
||||
|
||||
// Execute enable download
|
||||
const executeEnableDownload = () => {
|
||||
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: 'enable_download',
|
||||
media_ids: selectedIds,
|
||||
}),
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to enable download');
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then((data) => {
|
||||
showNotificationMessage(translateString('Successfully Enabled Download'));
|
||||
clearSelection();
|
||||
})
|
||||
.catch((error) => {
|
||||
showNotificationMessage(translateString('Failed to enable download.'), 'error');
|
||||
clearSelection();
|
||||
});
|
||||
};
|
||||
|
||||
// Execute disable download
|
||||
const executeDisableDownload = () => {
|
||||
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: 'disable_download',
|
||||
media_ids: selectedIds,
|
||||
}),
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to disable download');
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then((data) => {
|
||||
showNotificationMessage(translateString('Successfully Disabled Download'));
|
||||
clearSelection();
|
||||
})
|
||||
.catch((error) => {
|
||||
showNotificationMessage(translateString('Failed to disable download.'), 'error');
|
||||
clearSelection();
|
||||
});
|
||||
};
|
||||
|
||||
// Execute copy media
|
||||
const executeCopyMedia = () => {
|
||||
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: 'copy_media',
|
||||
media_ids: selectedIds,
|
||||
}),
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to copy media');
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then((data) => {
|
||||
showNotificationMessage(translateString('Successfully Copied'));
|
||||
clearSelectionAndRefresh();
|
||||
})
|
||||
.catch((error) => {
|
||||
showNotificationMessage(translateString('Failed to copy media.'), 'error');
|
||||
clearSelection();
|
||||
});
|
||||
};
|
||||
|
||||
// Permission modal handlers
|
||||
const handlePermissionModalCancel = () => {
|
||||
setShowPermissionModal(false);
|
||||
setPermissionType(null);
|
||||
};
|
||||
|
||||
const handlePermissionModalSuccess = (message) => {
|
||||
showNotificationMessage(message);
|
||||
clearSelection();
|
||||
setShowPermissionModal(false);
|
||||
setPermissionType(null);
|
||||
};
|
||||
|
||||
const handlePermissionModalError = (message) => {
|
||||
showNotificationMessage(message, 'error');
|
||||
setShowPermissionModal(false);
|
||||
setPermissionType(null);
|
||||
};
|
||||
|
||||
// Playlist modal handlers
|
||||
const handlePlaylistModalCancel = () => {
|
||||
setShowPlaylistModal(false);
|
||||
};
|
||||
|
||||
const handlePlaylistModalSuccess = (message) => {
|
||||
showNotificationMessage(message);
|
||||
clearSelection();
|
||||
setShowPlaylistModal(false);
|
||||
};
|
||||
|
||||
const handlePlaylistModalError = (message) => {
|
||||
showNotificationMessage(message, 'error');
|
||||
setShowPlaylistModal(false);
|
||||
};
|
||||
|
||||
// Change owner modal handlers
|
||||
const handleChangeOwnerModalCancel = () => {
|
||||
setShowChangeOwnerModal(false);
|
||||
};
|
||||
|
||||
const handleChangeOwnerModalSuccess = (message) => {
|
||||
showNotificationMessage(message);
|
||||
clearSelectionAndRefresh();
|
||||
setShowChangeOwnerModal(false);
|
||||
};
|
||||
|
||||
const handleChangeOwnerModalError = (message) => {
|
||||
showNotificationMessage(message, 'error');
|
||||
setShowChangeOwnerModal(false);
|
||||
};
|
||||
|
||||
// Publish state modal handlers
|
||||
const handlePublishStateModalCancel = () => {
|
||||
setShowPublishStateModal(false);
|
||||
};
|
||||
|
||||
const handlePublishStateModalSuccess = (message) => {
|
||||
showNotificationMessage(message);
|
||||
clearSelectionAndRefresh();
|
||||
setShowPublishStateModal(false);
|
||||
};
|
||||
|
||||
const handlePublishStateModalError = (message) => {
|
||||
showNotificationMessage(message, 'error');
|
||||
setShowPublishStateModal(false);
|
||||
};
|
||||
|
||||
// Category modal handlers
|
||||
const handleCategoryModalCancel = () => {
|
||||
setShowCategoryModal(false);
|
||||
};
|
||||
|
||||
const handleCategoryModalSuccess = (message) => {
|
||||
showNotificationMessage(message);
|
||||
clearSelection();
|
||||
setShowCategoryModal(false);
|
||||
};
|
||||
|
||||
const handleCategoryModalError = (message) => {
|
||||
showNotificationMessage(message, 'error');
|
||||
setShowCategoryModal(false);
|
||||
};
|
||||
|
||||
// Tag modal handlers
|
||||
const handleTagModalCancel = () => {
|
||||
setShowTagModal(false);
|
||||
};
|
||||
|
||||
const handleTagModalSuccess = (message) => {
|
||||
showNotificationMessage(message);
|
||||
clearSelection();
|
||||
setShowTagModal(false);
|
||||
};
|
||||
|
||||
const handleTagModalError = (message) => {
|
||||
showNotificationMessage(message, 'error');
|
||||
setShowTagModal(false);
|
||||
};
|
||||
|
||||
return {
|
||||
// State
|
||||
selectedMedia,
|
||||
availableMediaIds,
|
||||
listKey,
|
||||
showConfirmModal,
|
||||
confirmMessage,
|
||||
notificationMessage,
|
||||
showNotification,
|
||||
notificationType,
|
||||
showPermissionModal,
|
||||
permissionType,
|
||||
showPlaylistModal,
|
||||
showChangeOwnerModal,
|
||||
showPublishStateModal,
|
||||
showCategoryModal,
|
||||
showTagModal,
|
||||
|
||||
// Handlers
|
||||
handleMediaSelection,
|
||||
handleItemsUpdate,
|
||||
handleSelectAll,
|
||||
handleDeselectAll,
|
||||
handleBulkAction,
|
||||
handleConfirmCancel,
|
||||
handleConfirmProceed,
|
||||
handlePermissionModalCancel,
|
||||
handlePermissionModalSuccess,
|
||||
handlePermissionModalError,
|
||||
handlePlaylistModalCancel,
|
||||
handlePlaylistModalSuccess,
|
||||
handlePlaylistModalError,
|
||||
handleChangeOwnerModalCancel,
|
||||
handleChangeOwnerModalSuccess,
|
||||
handleChangeOwnerModalError,
|
||||
handlePublishStateModalCancel,
|
||||
handlePublishStateModalSuccess,
|
||||
handlePublishStateModalError,
|
||||
handleCategoryModalCancel,
|
||||
handleCategoryModalSuccess,
|
||||
handleCategoryModalError,
|
||||
handleTagModalCancel,
|
||||
handleTagModalSuccess,
|
||||
handleTagModalError,
|
||||
|
||||
// Utility
|
||||
getCsrfToken,
|
||||
clearSelection,
|
||||
clearSelectionAndRefresh,
|
||||
};
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 85 KiB |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user