mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Convert ItemsContainer to react
This commit is contained in:
parent
71e431d562
commit
c767dba439
7 changed files with 568 additions and 39 deletions
13
package-lock.json
generated
13
package-lock.json
generated
|
@ -74,6 +74,7 @@
|
|||
"@types/lodash-es": "4.17.7",
|
||||
"@types/react": "17.0.59",
|
||||
"@types/react-dom": "17.0.20",
|
||||
"@types/sortablejs": "1.15.3",
|
||||
"@typescript-eslint/eslint-plugin": "5.59.7",
|
||||
"@typescript-eslint/parser": "5.59.7",
|
||||
"@uupaa/dynamic-import-polyfill": "1.0.2",
|
||||
|
@ -4317,6 +4318,12 @@
|
|||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/sortablejs": {
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/sortablejs/-/sortablejs-1.15.3.tgz",
|
||||
"integrity": "sha512-v+zh6TZP/cLeMUK0MDx1onp8e7Jk2/4iTQ7sb/n80rTAvBm14yJkpOEfJdrTCkHiF7IZbPjxGX2NRJfogRoYIg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/trusted-types": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.3.tgz",
|
||||
|
@ -24923,6 +24930,12 @@
|
|||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/sortablejs": {
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/sortablejs/-/sortablejs-1.15.3.tgz",
|
||||
"integrity": "sha512-v+zh6TZP/cLeMUK0MDx1onp8e7Jk2/4iTQ7sb/n80rTAvBm14yJkpOEfJdrTCkHiF7IZbPjxGX2NRJfogRoYIg==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/trusted-types": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.3.tgz",
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
"@types/lodash-es": "4.17.7",
|
||||
"@types/react": "17.0.59",
|
||||
"@types/react-dom": "17.0.20",
|
||||
"@types/sortablejs": "1.15.3",
|
||||
"@typescript-eslint/eslint-plugin": "5.59.7",
|
||||
"@typescript-eslint/parser": "5.59.7",
|
||||
"@uupaa/dynamic-import-polyfill": "1.0.2",
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
import React, { FC, useEffect, useRef } from 'react';
|
||||
|
||||
import ItemsContainerElement from 'elements/ItemsContainerElement';
|
||||
import imageLoader from 'components/images/imageLoader';
|
||||
import 'elements/emby-itemscontainer/emby-itemscontainer';
|
||||
import { LibraryViewSettings, ViewMode } from 'types/library';
|
||||
|
||||
interface ItemsContainerI {
|
||||
libraryViewSettings: LibraryViewSettings;
|
||||
getItemsHtml: () => string
|
||||
}
|
||||
|
||||
const ItemsContainer: FC<ItemsContainerI> = ({ libraryViewSettings, getItemsHtml }) => {
|
||||
const element = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const itemsContainer = element.current?.querySelector('.itemsContainer') as HTMLDivElement;
|
||||
itemsContainer.innerHTML = getItemsHtml();
|
||||
imageLoader.lazyChildren(itemsContainer);
|
||||
}, [getItemsHtml]);
|
||||
|
||||
const cssClass = libraryViewSettings.ViewMode === ViewMode.ListView ? 'vertical-list' : 'vertical-wrap';
|
||||
|
||||
return (
|
||||
<div ref={element}>
|
||||
<ItemsContainerElement
|
||||
className={`itemsContainer ${cssClass} centered padded-left padded-right padded-right-withalphapicker`}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ItemsContainer;
|
|
@ -11,9 +11,9 @@ import listview from 'components/listview/listview';
|
|||
import cardBuilder from 'components/cardbuilder/cardBuilder';
|
||||
import { playbackManager } from 'components/playback/playbackmanager';
|
||||
import globalize from 'scripts/globalize';
|
||||
import ItemsContainer from 'elements/emby-itemscontainer/ItemsContainer';
|
||||
import AlphabetPicker from './AlphabetPicker';
|
||||
import FilterButton from './filter/FilterButton';
|
||||
import ItemsContainer from './ItemsContainer';
|
||||
import NewCollectionButton from './NewCollectionButton';
|
||||
import Pagination from './Pagination';
|
||||
import PlayAllButton from './PlayAllButton';
|
||||
|
@ -67,7 +67,8 @@ const ItemsView: FC<ItemsViewProps> = ({
|
|||
const {
|
||||
isLoading,
|
||||
data: itemsResult,
|
||||
isPreviousData
|
||||
isPreviousData,
|
||||
refetch
|
||||
} = useGetItemsViewByType(
|
||||
viewType,
|
||||
parentId,
|
||||
|
@ -252,7 +253,10 @@ const ItemsView: FC<ItemsViewProps> = ({
|
|||
<Loading />
|
||||
) : (
|
||||
<ItemsContainer
|
||||
className='centered padded-left padded-right padded-right-withalphapicker'
|
||||
libraryViewSettings={libraryViewSettings}
|
||||
parentId={parentId}
|
||||
reloadItems={refetch}
|
||||
getItemsHtml={getItemsHtml}
|
||||
/>
|
||||
)}
|
||||
|
|
522
src/elements/emby-itemscontainer/ItemsContainer.tsx
Normal file
522
src/elements/emby-itemscontainer/ItemsContainer.tsx
Normal file
|
@ -0,0 +1,522 @@
|
|||
import type {
|
||||
LibraryUpdateInfo,
|
||||
SeriesTimerInfoDto,
|
||||
TimerInfoDto,
|
||||
UserItemDataDto
|
||||
} from '@jellyfin/sdk/lib/generated-client';
|
||||
import React, { FC, useCallback, useEffect, useRef } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Box } from '@mui/material';
|
||||
import Sortable from 'sortablejs';
|
||||
import { usePlaylistsMoveItemMutation } from 'hooks/useFetchItems';
|
||||
import Events, { Event } from 'utils/events';
|
||||
import serverNotifications from 'scripts/serverNotifications';
|
||||
import inputManager from 'scripts/inputManager';
|
||||
import dom from 'scripts/dom';
|
||||
import browser from 'scripts/browser';
|
||||
import imageLoader from 'components/images/imageLoader';
|
||||
import layoutManager from 'components/layoutManager';
|
||||
import { playbackManager } from 'components/playback/playbackmanager';
|
||||
import itemShortcuts from 'components/shortcuts';
|
||||
import MultiSelect from 'components/multiSelect/multiSelect';
|
||||
import loading from 'components/loading/loading';
|
||||
import focusManager from 'components/focusManager';
|
||||
import { LibraryViewSettings, ParentId, ViewMode } from 'types/library';
|
||||
|
||||
function disableEvent(e: MouseEvent) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
|
||||
function getShortcutOptions() {
|
||||
return {
|
||||
click: false
|
||||
};
|
||||
}
|
||||
|
||||
interface ItemsContainerProps {
|
||||
className?: string;
|
||||
libraryViewSettings: LibraryViewSettings;
|
||||
isContextMenuEnabled?: boolean;
|
||||
isMultiSelectEnabled?: boolean;
|
||||
isDragreOrderEnabled?: boolean;
|
||||
dataMonitor?: string;
|
||||
parentId?: ParentId;
|
||||
reloadItems: () => void;
|
||||
getItemsHtml?: () => string;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
const ItemsContainer: FC<ItemsContainerProps> = ({
|
||||
className,
|
||||
libraryViewSettings,
|
||||
isContextMenuEnabled,
|
||||
isMultiSelectEnabled,
|
||||
isDragreOrderEnabled,
|
||||
dataMonitor,
|
||||
parentId,
|
||||
reloadItems,
|
||||
getItemsHtml,
|
||||
children
|
||||
}) => {
|
||||
const { mutateAsync: playlistsMoveItemMutation } = usePlaylistsMoveItemMutation();
|
||||
const itemsContainerRef = useRef<HTMLDivElement>(null);
|
||||
const multiSelectref = useRef<MultiSelect | null>(null);
|
||||
const sortableref = useRef<Sortable | null>(null);
|
||||
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
const onClick = useCallback((e: MouseEvent) => {
|
||||
const itemsContainer = itemsContainerRef.current as HTMLDivElement;
|
||||
const multiSelect = multiSelectref.current;
|
||||
|
||||
if (
|
||||
multiSelect
|
||||
&& multiSelect.onContainerClick.call(itemsContainer, e) === false
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
itemShortcuts.onClick.call(itemsContainer, e);
|
||||
}, []);
|
||||
|
||||
const onContextMenu = useCallback((e: MouseEvent) => {
|
||||
const target = e.target as HTMLElement;
|
||||
const card = dom.parentWithAttribute(target, 'data-id');
|
||||
|
||||
// check for serverId, it won't be present on selectserver
|
||||
if (card?.getAttribute('data-serverid')) {
|
||||
inputManager.handleCommand('menu', {
|
||||
sourceElement: card
|
||||
});
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const initMultiSelect = useCallback((itemsContainer: HTMLDivElement) => {
|
||||
multiSelectref.current = new MultiSelect({
|
||||
container: itemsContainer,
|
||||
bindOnClick: false
|
||||
});
|
||||
}, []);
|
||||
|
||||
const destroyMultiSelect = useCallback(() => {
|
||||
if (multiSelectref.current) {
|
||||
multiSelectref.current.destroy();
|
||||
multiSelectref.current = null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onDrop = useCallback(
|
||||
async (evt: Sortable.SortableEvent) => {
|
||||
const el = evt.item;
|
||||
|
||||
const newIndex = evt.newIndex;
|
||||
const itemId = el.getAttribute('data-playlistitemid');
|
||||
const playlistId = el.getAttribute('data-playlistid');
|
||||
|
||||
if (!playlistId) {
|
||||
const oldIndex = evt.oldIndex;
|
||||
el.dispatchEvent(
|
||||
new CustomEvent('itemdrop', {
|
||||
detail: {
|
||||
oldIndex: oldIndex,
|
||||
newIndex: newIndex,
|
||||
playlistItemId: itemId
|
||||
},
|
||||
bubbles: true,
|
||||
cancelable: false
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!itemId) throw new Error('null itemId');
|
||||
if (!newIndex) throw new Error('null newIndex');
|
||||
|
||||
try {
|
||||
loading.show();
|
||||
await playlistsMoveItemMutation({
|
||||
playlistId,
|
||||
itemId,
|
||||
newIndex
|
||||
});
|
||||
loading.hide();
|
||||
} catch (error) {
|
||||
loading.hide();
|
||||
reloadItems();
|
||||
}
|
||||
},
|
||||
[playlistsMoveItemMutation, reloadItems]
|
||||
);
|
||||
|
||||
const initDragReordering = useCallback((itemsContainer: HTMLDivElement) => {
|
||||
sortableref.current = Sortable.create(itemsContainer, {
|
||||
draggable: '.listItem',
|
||||
handle: '.listViewDragHandle',
|
||||
|
||||
// dragging ended
|
||||
onEnd: (evt: Sortable.SortableEvent) => {
|
||||
return onDrop(evt);
|
||||
}
|
||||
});
|
||||
}, [onDrop]);
|
||||
|
||||
const destroyDragReordering = useCallback(() => {
|
||||
if (sortableref.current) {
|
||||
sortableref.current.destroy();
|
||||
sortableref.current = null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const notifyRefreshNeeded = useCallback(
|
||||
(isInForeground: boolean) => {
|
||||
if (isInForeground === true) {
|
||||
reloadItems();
|
||||
} else {
|
||||
timerRef.current = setTimeout(() => reloadItems(), 10000);
|
||||
}
|
||||
},
|
||||
[reloadItems]
|
||||
);
|
||||
|
||||
const getEventsToMonitor = useCallback(() => {
|
||||
const monitor = dataMonitor;
|
||||
if (monitor) {
|
||||
return monitor.split(',');
|
||||
}
|
||||
|
||||
return [];
|
||||
}, [dataMonitor]);
|
||||
|
||||
const onUserDataChanged = useCallback(
|
||||
(_e: Event, userData: UserItemDataDto) => {
|
||||
const itemsContainer = itemsContainerRef.current as HTMLDivElement;
|
||||
|
||||
import('../../components/cardbuilder/cardBuilder')
|
||||
.then((cardBuilder) => {
|
||||
cardBuilder.onUserDataChanged(userData, itemsContainer);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(
|
||||
'[onUserDataChanged] failed to load onUserData Changed',
|
||||
err
|
||||
);
|
||||
});
|
||||
|
||||
const eventsToMonitor = getEventsToMonitor();
|
||||
if (
|
||||
eventsToMonitor.indexOf('markfavorite') !== -1
|
||||
|| eventsToMonitor.indexOf('markplayed') !== -1
|
||||
) {
|
||||
notifyRefreshNeeded(false);
|
||||
}
|
||||
},
|
||||
[getEventsToMonitor, notifyRefreshNeeded]
|
||||
);
|
||||
|
||||
const onTimerCreated = useCallback(
|
||||
(_e: Event, data: TimerInfoDto) => {
|
||||
const itemsContainer = itemsContainerRef.current as HTMLDivElement;
|
||||
const eventsToMonitor = getEventsToMonitor();
|
||||
if (eventsToMonitor.indexOf('timers') !== -1) {
|
||||
notifyRefreshNeeded(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const programId = data.ProgramId;
|
||||
// This could be null, not supported by all tv providers
|
||||
const newTimerId = data.Id;
|
||||
if (programId && newTimerId) {
|
||||
import('../../components/cardbuilder/cardBuilder')
|
||||
.then((cardBuilder) => {
|
||||
cardBuilder.onTimerCreated(
|
||||
programId,
|
||||
newTimerId,
|
||||
itemsContainer
|
||||
);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(
|
||||
'[onTimerCreated] failed to load onTimer Created',
|
||||
err
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
[getEventsToMonitor, notifyRefreshNeeded]
|
||||
);
|
||||
|
||||
const onSeriesTimerCreated = useCallback(() => {
|
||||
const eventsToMonitor = getEventsToMonitor();
|
||||
if (eventsToMonitor.indexOf('seriestimers') !== -1) {
|
||||
notifyRefreshNeeded(false);
|
||||
}
|
||||
}, [getEventsToMonitor, notifyRefreshNeeded]);
|
||||
|
||||
const onTimerCancelled = useCallback(
|
||||
(_e: Event, data: TimerInfoDto) => {
|
||||
const itemsContainer = itemsContainerRef.current as HTMLDivElement;
|
||||
const eventsToMonitor = getEventsToMonitor();
|
||||
if (eventsToMonitor.indexOf('timers') !== -1) {
|
||||
notifyRefreshNeeded(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const timerId = data.Id;
|
||||
|
||||
if (timerId) {
|
||||
import('../../components/cardbuilder/cardBuilder')
|
||||
.then((cardBuilder) => {
|
||||
cardBuilder.onTimerCancelled(timerId, itemsContainer);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(
|
||||
'[onTimerCancelled] failed to load onTimer Cancelled',
|
||||
err
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
[getEventsToMonitor, notifyRefreshNeeded]
|
||||
);
|
||||
|
||||
const onSeriesTimerCancelled = useCallback(
|
||||
(_e: Event, data: SeriesTimerInfoDto) => {
|
||||
const itemsContainer = itemsContainerRef.current as HTMLDivElement;
|
||||
const eventsToMonitor = getEventsToMonitor();
|
||||
if (eventsToMonitor.indexOf('seriestimers') !== -1) {
|
||||
notifyRefreshNeeded(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const cancelledTimerId = data.Id;
|
||||
|
||||
if (cancelledTimerId) {
|
||||
import('../../components/cardbuilder/cardBuilder')
|
||||
.then((cardBuilder) => {
|
||||
cardBuilder.onSeriesTimerCancelled(
|
||||
cancelledTimerId,
|
||||
itemsContainer
|
||||
);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(
|
||||
'[onSeriesTimerCancelled] failed to load onSeriesTimer Cancelled',
|
||||
err
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
[getEventsToMonitor, notifyRefreshNeeded]
|
||||
);
|
||||
|
||||
const onLibraryChanged = useCallback(
|
||||
(_e: Event, data: LibraryUpdateInfo) => {
|
||||
const eventsToMonitor = getEventsToMonitor();
|
||||
if (
|
||||
eventsToMonitor.indexOf('seriestimers') !== -1
|
||||
|| eventsToMonitor.indexOf('timers') !== -1
|
||||
) {
|
||||
// yes this is an assumption
|
||||
return;
|
||||
}
|
||||
|
||||
const itemsAdded = data.ItemsAdded ?? [];
|
||||
const itemsRemoved = data.ItemsRemoved ?? [];
|
||||
if (!itemsAdded.length && !itemsRemoved.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (parentId) {
|
||||
const foldersAddedTo = data.FoldersAddedTo ?? [];
|
||||
const foldersRemovedFrom = data.FoldersRemovedFrom ?? [];
|
||||
const collectionFolders = data.CollectionFolders ?? [];
|
||||
|
||||
if (
|
||||
foldersAddedTo.indexOf(parentId) === -1
|
||||
&& foldersRemovedFrom.indexOf(parentId) === -1
|
||||
&& collectionFolders.indexOf(parentId) === -1
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
notifyRefreshNeeded(false);
|
||||
},
|
||||
[getEventsToMonitor, notifyRefreshNeeded, parentId]
|
||||
);
|
||||
|
||||
const onPlaybackStopped = useCallback(
|
||||
(_e: Event, stopInfo) => {
|
||||
const state = stopInfo.state;
|
||||
|
||||
const eventsToMonitor = getEventsToMonitor();
|
||||
if (
|
||||
state.NowPlayingItem
|
||||
&& state.NowPlayingItem.MediaType === 'Video'
|
||||
) {
|
||||
if (eventsToMonitor.indexOf('videoplayback') !== -1) {
|
||||
notifyRefreshNeeded(true);
|
||||
return;
|
||||
}
|
||||
} else if (
|
||||
state.NowPlayingItem
|
||||
&& state.NowPlayingItem.MediaType === 'Audio'
|
||||
&& eventsToMonitor.indexOf('audioplayback') !== -1
|
||||
) {
|
||||
notifyRefreshNeeded(true);
|
||||
return;
|
||||
}
|
||||
},
|
||||
[getEventsToMonitor, notifyRefreshNeeded]
|
||||
);
|
||||
|
||||
const setFocus = useCallback(
|
||||
(
|
||||
itemsContainer: HTMLDivElement,
|
||||
focusId: string | null | undefined
|
||||
) => {
|
||||
if (focusId) {
|
||||
const newElement = itemsContainer.querySelector(
|
||||
'[data-id="' + focusId + '"]'
|
||||
);
|
||||
if (newElement) {
|
||||
try {
|
||||
focusManager.focus(newElement);
|
||||
return;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
focusManager.autoFocus(itemsContainer);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const itemsContainer = itemsContainerRef.current;
|
||||
|
||||
if (!itemsContainer) {
|
||||
console.error('Unexpected null reference');
|
||||
return;
|
||||
}
|
||||
|
||||
const activeElement = document.activeElement;
|
||||
let focusId;
|
||||
let hasActiveElement;
|
||||
if (itemsContainer?.contains(activeElement)) {
|
||||
hasActiveElement = true;
|
||||
focusId = activeElement?.getAttribute('data-id');
|
||||
}
|
||||
|
||||
if (getItemsHtml) {
|
||||
itemsContainer.innerHTML = getItemsHtml();
|
||||
}
|
||||
|
||||
imageLoader.lazyChildren(itemsContainer);
|
||||
|
||||
if (hasActiveElement) {
|
||||
setFocus(itemsContainer, focusId);
|
||||
}
|
||||
}, [getItemsHtml, setFocus]);
|
||||
|
||||
useEffect(() => {
|
||||
const itemsContainer = itemsContainerRef.current;
|
||||
|
||||
if (!itemsContainer) {
|
||||
console.error('Unexpected null reference');
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
layoutManager.desktop
|
||||
|| (layoutManager.mobile && isMultiSelectEnabled !== false)
|
||||
) {
|
||||
initMultiSelect(itemsContainer);
|
||||
}
|
||||
|
||||
if (isDragreOrderEnabled === true) {
|
||||
initDragReordering(itemsContainer);
|
||||
}
|
||||
|
||||
itemsContainer.addEventListener('click', onClick);
|
||||
|
||||
if (browser.touch) {
|
||||
itemsContainer.addEventListener('contextmenu', disableEvent);
|
||||
} else if (isContextMenuEnabled !== false) {
|
||||
itemsContainer.addEventListener('contextmenu', onContextMenu);
|
||||
}
|
||||
|
||||
itemShortcuts.on(itemsContainer, getShortcutOptions());
|
||||
|
||||
Events.on(serverNotifications, 'UserDataChanged', onUserDataChanged);
|
||||
Events.on(serverNotifications, 'TimerCreated', onTimerCreated);
|
||||
Events.on(serverNotifications, 'TimerCancelled', onTimerCancelled);
|
||||
Events.on(serverNotifications, 'SeriesTimerCreated', onSeriesTimerCreated);
|
||||
Events.on(serverNotifications, 'SeriesTimerCancelled', onSeriesTimerCancelled);
|
||||
Events.on(serverNotifications, 'LibraryChanged', onLibraryChanged);
|
||||
Events.on(playbackManager, 'playbackstop', onPlaybackStopped);
|
||||
|
||||
return () => {
|
||||
if (timerRef.current) {
|
||||
clearTimeout(timerRef.current);
|
||||
}
|
||||
|
||||
destroyMultiSelect();
|
||||
destroyDragReordering();
|
||||
itemsContainer.removeEventListener('click', onClick);
|
||||
itemsContainer.removeEventListener('contextmenu', onContextMenu);
|
||||
itemsContainer.removeEventListener('contextmenu', disableEvent);
|
||||
|
||||
itemShortcuts.off(itemsContainer, getShortcutOptions());
|
||||
|
||||
Events.off(serverNotifications, 'UserDataChanged', onUserDataChanged);
|
||||
Events.off(serverNotifications, 'TimerCreated', onTimerCreated);
|
||||
Events.off(serverNotifications, 'TimerCancelled', onTimerCancelled);
|
||||
Events.off( serverNotifications, 'SeriesTimerCreated', onSeriesTimerCreated);
|
||||
Events.off(serverNotifications, 'SeriesTimerCancelled', onSeriesTimerCancelled);
|
||||
Events.off(serverNotifications, 'LibraryChanged', onLibraryChanged);
|
||||
Events.off(playbackManager, 'playbackstop', onPlaybackStopped);
|
||||
};
|
||||
}, [
|
||||
destroyDragReordering,
|
||||
destroyMultiSelect,
|
||||
initDragReordering,
|
||||
initMultiSelect,
|
||||
isContextMenuEnabled,
|
||||
isDragreOrderEnabled,
|
||||
isMultiSelectEnabled,
|
||||
onClick,
|
||||
onContextMenu,
|
||||
onLibraryChanged,
|
||||
onPlaybackStopped,
|
||||
onSeriesTimerCancelled,
|
||||
onSeriesTimerCreated,
|
||||
onTimerCancelled,
|
||||
onTimerCreated,
|
||||
onUserDataChanged
|
||||
]);
|
||||
|
||||
const itemsContainerClass = classNames(
|
||||
'itemsContainer',
|
||||
{ 'itemsContainer-tv': layoutManager.tv },
|
||||
libraryViewSettings.ViewMode === ViewMode.ListView ?
|
||||
'vertical-list' :
|
||||
'vertical-wrap',
|
||||
className
|
||||
);
|
||||
|
||||
return (
|
||||
<Box ref={itemsContainerRef} className={itemsContainerClass}>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ItemsContainer;
|
|
@ -1,4 +1,5 @@
|
|||
import type { ItemsApiGetItemsRequest } from '@jellyfin/sdk/lib/generated-client';
|
||||
import { AxiosRequestConfig } from 'axios';
|
||||
import type { ItemsApiGetItemsRequest, PlaylistsApiMoveItemRequest } from '@jellyfin/sdk/lib/generated-client';
|
||||
import type { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
|
||||
import { ImageType } from '@jellyfin/sdk/lib/generated-client/models/image-type';
|
||||
import { ItemFields } from '@jellyfin/sdk/lib/generated-client/models/item-fields';
|
||||
|
@ -13,8 +14,8 @@ import { getMoviesApi } from '@jellyfin/sdk/lib/utils/api/movies-api';
|
|||
import { getStudiosApi } from '@jellyfin/sdk/lib/utils/api/studios-api';
|
||||
import { getTvShowsApi } from '@jellyfin/sdk/lib/utils/api/tv-shows-api';
|
||||
import { getUserLibraryApi } from '@jellyfin/sdk/lib/utils/api/user-library-api';
|
||||
import { AxiosRequestConfig } from 'axios';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { getPlaylistsApi } from '@jellyfin/sdk/lib/utils/api/playlists-api';
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { JellyfinApiContext, useApi } from './useApi';
|
||||
import { getAlphaPickerQuery, getFieldsQuery, getFiltersQuery, getLimitQuery } from 'utils/items';
|
||||
|
@ -509,3 +510,24 @@ export const useGetItemsViewByType = (
|
|||
].includes(viewType) && !!parentId
|
||||
});
|
||||
};
|
||||
|
||||
const fetchPlaylistsMoveItem = async (
|
||||
currentApi: JellyfinApiContext,
|
||||
requestParameters: PlaylistsApiMoveItemRequest
|
||||
) => {
|
||||
const { api, user } = currentApi;
|
||||
if (api && user?.Id) {
|
||||
const response = await getPlaylistsApi(api).moveItem({
|
||||
...requestParameters
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
};
|
||||
|
||||
export const usePlaylistsMoveItemMutation = () => {
|
||||
const currentApi = useApi();
|
||||
return useMutation({
|
||||
mutationFn: (requestParameters: PlaylistsApiMoveItemRequest) =>
|
||||
fetchPlaylistsMoveItem(currentApi, requestParameters )
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
interface Event {
|
||||
export interface Event {
|
||||
type: string;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue