mirror of
https://github.com/jeffvli/feishin.git
synced 2026-06-14 23:44:01 +02:00
support image drop for upload
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useLocation, useParams } from 'react-router';
|
||||
|
||||
@@ -40,8 +41,13 @@ interface PlaylistDetailSongListHeaderProps {
|
||||
onToggleQueryBuilder?: () => void;
|
||||
}
|
||||
|
||||
function ImageUploadOverlay({ data }: { data?: Playlist }) {
|
||||
const uploadPlaylistImageMutation = useUploadPlaylistImage({});
|
||||
function ImageUploadOverlay({
|
||||
data,
|
||||
onUploadFile,
|
||||
}: {
|
||||
data?: Playlist;
|
||||
onUploadFile: (file: File) => Promise<void>;
|
||||
}) {
|
||||
const deletePlaylistImageMutation = useDeletePlaylistImage({});
|
||||
const server = useCurrentServer();
|
||||
|
||||
@@ -53,16 +59,8 @@ function ImageUploadOverlay({ data }: { data?: Playlist }) {
|
||||
<FileButton
|
||||
accept="image/*"
|
||||
onChange={async (file) => {
|
||||
if (!file || !data?._serverId) return;
|
||||
|
||||
const buffer = await file.arrayBuffer();
|
||||
uploadPlaylistImageMutation.mutate({
|
||||
apiClientProps: {
|
||||
serverId: data._serverId,
|
||||
},
|
||||
body: { image: new Uint8Array(buffer) },
|
||||
query: { id: data.id },
|
||||
});
|
||||
if (!file) return;
|
||||
await onUploadFile(file);
|
||||
}}
|
||||
>
|
||||
{(props) => (
|
||||
@@ -121,11 +119,32 @@ export const PlaylistDetailSongListHeader = ({
|
||||
});
|
||||
|
||||
const player = usePlayer();
|
||||
const uploadPlaylistImageMutation = useUploadPlaylistImage({});
|
||||
|
||||
const handlePlay = (type?: Play) => {
|
||||
player.addToQueueByData(listData as Song[], type || Play.NOW);
|
||||
};
|
||||
|
||||
const canUploadPlaylistImage =
|
||||
hasFeature(server, ServerFeature.PLAYLIST_IMAGE_UPLOAD) && Boolean(detailQuery?.data?._serverId);
|
||||
|
||||
const handlePlaylistImageUpload = useCallback(
|
||||
async (file: File) => {
|
||||
const playlist = detailQuery?.data;
|
||||
if (!playlist?._serverId) return;
|
||||
|
||||
const buffer = await file.arrayBuffer();
|
||||
uploadPlaylistImageMutation.mutate({
|
||||
apiClientProps: {
|
||||
serverId: playlist._serverId,
|
||||
},
|
||||
body: { image: new Uint8Array(buffer) },
|
||||
query: { id: playlist.id },
|
||||
});
|
||||
},
|
||||
[detailQuery?.data, uploadPlaylistImageMutation],
|
||||
);
|
||||
|
||||
const imageUrl = useItemImageUrl({
|
||||
id: detailQuery?.data?.imageId || undefined,
|
||||
itemType: LibraryItem.PLAYLIST,
|
||||
@@ -163,7 +182,12 @@ export const PlaylistDetailSongListHeader = ({
|
||||
) : (
|
||||
<LibraryHeader
|
||||
compact
|
||||
imageOverlay={<ImageUploadOverlay data={detailQuery?.data} />}
|
||||
imageOverlay={
|
||||
<ImageUploadOverlay
|
||||
data={detailQuery?.data}
|
||||
onUploadFile={handlePlaylistImageUpload}
|
||||
/>
|
||||
}
|
||||
imageUrl={imageUrl}
|
||||
item={{
|
||||
imageId: detailQuery?.data?.imageId,
|
||||
@@ -171,6 +195,7 @@ export const PlaylistDetailSongListHeader = ({
|
||||
route: AppRoute.PLAYLISTS,
|
||||
type: LibraryItem.PLAYLIST,
|
||||
}}
|
||||
onImageFileDrop={canUploadPlaylistImage ? handlePlaylistImageUpload : undefined}
|
||||
title={detailQuery?.data?.name || ''}
|
||||
topRight={<ListSearchInput />}
|
||||
>
|
||||
|
||||
@@ -13,6 +13,7 @@ import { useCurrentServer, useCurrentServerId, usePermissions } from '/@/rendere
|
||||
import { hasFeature } from '/@/shared/api/utils';
|
||||
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
|
||||
import { Box } from '/@/shared/components/box/box';
|
||||
import { DragDropZone } from '/@/shared/components/drag-drop-zone/drag-drop-zone';
|
||||
import { FileButton } from '/@/shared/components/file-button/file-button';
|
||||
import { Flex } from '/@/shared/components/flex/flex';
|
||||
import { Group } from '/@/shared/components/group/group';
|
||||
@@ -270,16 +271,20 @@ function PlaylistCoverField({
|
||||
const iconControls = (
|
||||
<>
|
||||
<FileButton accept="image/*" onChange={onFileSelect}>
|
||||
{(props) => (
|
||||
<ActionIcon
|
||||
icon="uploadImage"
|
||||
iconProps={{ size: 'lg' }}
|
||||
radius="xl"
|
||||
size="sm"
|
||||
variant="default"
|
||||
{...props}
|
||||
/>
|
||||
)}
|
||||
{(props) => {
|
||||
const { ...triggerRest } = props;
|
||||
return (
|
||||
<ActionIcon
|
||||
icon="uploadImage"
|
||||
iconProps={{ size: 'lg' }}
|
||||
radius="xl"
|
||||
size="sm"
|
||||
variant="default"
|
||||
{...triggerRest}
|
||||
style={{ pointerEvents: 'auto' }}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</FileButton>
|
||||
<ActionIcon
|
||||
disabled={secondaryDisabled}
|
||||
@@ -288,22 +293,12 @@ function PlaylistCoverField({
|
||||
onClick={secondaryAction}
|
||||
radius="xl"
|
||||
size="sm"
|
||||
style={{ pointerEvents: 'auto' }}
|
||||
variant="default"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
const coverArt = (
|
||||
<ItemImage
|
||||
enableViewport={false}
|
||||
id={previewId}
|
||||
itemType={LibraryItem.PLAYLIST}
|
||||
serverId={server?.id}
|
||||
src={previewSrc}
|
||||
type="header"
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Box
|
||||
style={{
|
||||
@@ -315,21 +310,41 @@ function PlaylistCoverField({
|
||||
width: COVER_SIZE,
|
||||
}}
|
||||
>
|
||||
{coverArt}
|
||||
<Group
|
||||
gap={4}
|
||||
<DragDropZone
|
||||
accept="image/*"
|
||||
mode="file"
|
||||
onFileSelected={(file) => onFileSelect(file)}
|
||||
style={{
|
||||
background: 'rgba(0, 0, 0, 0.55)',
|
||||
borderRadius: 'var(--mantine-radius-md)',
|
||||
bottom: 6,
|
||||
padding: 4,
|
||||
position: 'absolute',
|
||||
right: 6,
|
||||
height: '100%',
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
}}
|
||||
wrap="nowrap"
|
||||
>
|
||||
{iconControls}
|
||||
</Group>
|
||||
<ItemImage
|
||||
enableViewport={false}
|
||||
id={previewId}
|
||||
itemType={LibraryItem.PLAYLIST}
|
||||
serverId={server?.id}
|
||||
src={previewSrc}
|
||||
type="header"
|
||||
/>
|
||||
<Group
|
||||
gap={4}
|
||||
style={{
|
||||
background: 'rgba(0, 0, 0, 0.55)',
|
||||
bottom: 6,
|
||||
padding: 4,
|
||||
pointerEvents: 'none',
|
||||
position: 'absolute',
|
||||
right: 6,
|
||||
zIndex: 2,
|
||||
}}
|
||||
wrap="nowrap"
|
||||
>
|
||||
{iconControls}
|
||||
</Group>
|
||||
</DragDropZone>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user