Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions app/main.main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
dialog,
ipcMain as ipc,
Menu,
nativeImage,
nativeTheme,
net,
powerSaveBlocker,
Expand Down Expand Up @@ -3218,6 +3219,30 @@ ipc.on('show-item-in-folder', (_event, folder) => {
shell.showItemInFolder(folder);
});

let lastDragTempPath: string | null = null;


ipc.on('start-attachment-drag', (event, filePath: string) => {
if (lastDragTempPath) {
const stale = lastDragTempPath;
fsExtra.remove(stale).catch(err => {
log.warn('Failed to cleanup stale drag temp file:', Errors.toLogFormat(err));
});
}
lastDragTempPath = filePath;
const icon = nativeImage
.createFromPath(join(__dirname, '../images/file.png'))
.resize({ width: 32 });
event.sender.startDrag({ file: filePath, icon });
});

app.on('will-quit', () => {
if (lastDragTempPath) {
fsExtra.removeSync(lastDragTempPath);
lastDragTempPath = null;
}
});

ipc.handle('show-save-dialog', async (_event, { defaultPath }) => {
if (!mainWindow) {
log.warn('show-save-dialog: no main window');
Expand Down
Binary file added images/file.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
44 changes: 42 additions & 2 deletions ts/components/conversation/Message.dom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import type { ReadonlyDeep } from 'type-fest';
import type {
ConversationType,
ConversationTypeType,
DragAttachmentActionCreatorType,
InteractionModeType,
PushPanelForConversationActionType,
SaveAttachmentActionCreatorType,
Expand Down Expand Up @@ -375,6 +376,7 @@ export type PropsActions = {
attachment: AttachmentType;
messageId: string;
}) => void;
dragAttachment: DragAttachmentActionCreatorType;
saveAttachment: SaveAttachmentActionCreatorType;
saveAttachments: SaveAttachmentsActionCreatorType;
showLightbox: (options: {
Expand Down Expand Up @@ -1154,6 +1156,7 @@ export class Message extends PureComponent<Props, State> {
canRetryDeleteForEveryone,
cancelAttachmentDownload,
direction,
dragAttachment,
expirationLength,
expirationTimestamp,
i18n,
Expand Down Expand Up @@ -1223,8 +1226,19 @@ export class Message extends PureComponent<Props, State> {
);

if (isGIF(attachments)) {
const isGifDraggable =
!!firstAttachment.path && !isAttachmentNotAvailable;
return (
<div className={containerClassName}>
<div
className={containerClassName}
draggable={isGifDraggable}
onDragStart={(event: React.DragEvent) => {
event.preventDefault();
if (isGifDraggable) {
dragAttachment(firstAttachment, timestamp);
}
}}
>
{/* oxlint-disable-next-line react/jsx-pascal-case */}
<GIF
attachment={firstAttachment}
Expand Down Expand Up @@ -1260,9 +1274,23 @@ export class Message extends PureComponent<Props, State> {
const bottomOverlay = !isSticker && !collapseMetadata;
// We only want users to tab into this if there's more than one
const tabIndex = attachments.length > 1 ? 0 : -1;
const isMediaDraggable =
!isSticker &&
attachments.length === 1 &&
!!firstAttachment.path &&
!isAttachmentNotAvailable;

return (
<div className={containerClassName}>
<div
className={containerClassName}
draggable={isMediaDraggable}
onDragStart={(event: React.DragEvent) => {
event.preventDefault();
if (isMediaDraggable) {
dragAttachment(firstAttachment, timestamp);
}
}}
>
<ImageGrid
attachments={attachments}
direction={direction}
Expand Down Expand Up @@ -1345,7 +1373,18 @@ export class Message extends PureComponent<Props, State> {
// Note: this has to be interactive for the case where text comes along with the
// attachment. But we don't want the user to tab here unless that text exists.
const tabIndex = text ? 0 : -1;
const isDraggable = !!firstAttachment.path && !isAttachmentNotAvailable;
return (
<div
className="module-message__simple-attachment-container"
draggable={isDraggable}
onDragStart={(event: React.DragEvent) => {
event.preventDefault();
if (isDraggable) {
dragAttachment(firstAttachment, timestamp);
}
}}
>
<button
className={classNames(
'module-message__simple-attachment',
Expand Down Expand Up @@ -1452,6 +1491,7 @@ export class Message extends PureComponent<Props, State> {
</div>
</div>
</button>
</div>
);
}

Expand Down
1 change: 1 addition & 0 deletions ts/components/conversation/TimelineItem.dom.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ const getDefaultProps = () => ({
messageExpanded: action('messageExpanded'),
showConversation: action('showConversation'),
openGiftBadge: action('openGiftBadge'),
dragAttachment: action('dragAttachment'),
saveAttachment: action('saveAttachment'),
saveAttachments: action('saveAttachments'),
showPinMessageDialog: action('showPinMessageDialog'),
Expand Down
1 change: 1 addition & 0 deletions ts/components/conversation/TimelineMessage.dom.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
: overrideProps.readStatus,
renderReactionPicker,
renderAudioAttachment,
dragAttachment: action('dragAttachment'),
saveAttachment: action('saveAttachment'),
saveAttachments: action('saveAttachments'),
setQuoteByMessageId: action('setQuoteByMessageId'),
Expand Down
27 changes: 27 additions & 0 deletions ts/state/ducks/conversations.preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
readAttachmentData,
saveAttachmentToDisk,
} from '../../util/migrations.preload.ts';
import { TEMP_PATH } from '../../util/basePaths.preload.ts';
import type { DurationInSeconds } from '../../util/durations/index.std.ts';
import * as universalExpireTimer from '../../util/universalExpireTimer.preload.ts';
import * as Attachment from '../../util/Attachment.std.ts';
Expand Down Expand Up @@ -1233,6 +1234,7 @@ export const actions = {
saveAttachment,
saveAttachments,
saveAttachmentFromMessage,
dragAttachment,
saveAvatarToDisk,
scrollToMessage,
scrollToOldestUnreadMention,
Expand Down Expand Up @@ -4204,6 +4206,31 @@ function saveAttachment(
};
}

export type DragAttachmentActionCreatorType = ReadonlyDeep<
(attachment: AttachmentType, timestamp?: number) => unknown
>;

function dragAttachment(
attachment: AttachmentType,
timestamp = Date.now()
): ThunkAction<void, RootStateType, unknown, ShowToastActionType> {
return async () => {
const fullPath = await Attachment.save({
attachment,
getUnusedFilename,
readAttachmentData,
saveAttachmentToDisk,
timestamp,
baseDir: TEMP_PATH,
});
if (fullPath) {
ipcRenderer.send('start-attachment-drag', fullPath);
}
};
}



const showSaveMultiDialog = (
i18n: LocalizerType
): Promise<{
Expand Down
2 changes: 2 additions & 0 deletions ts/state/smart/TimelineItem.preload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ export const SmartTimelineItem = memo(function SmartTimelineItem(
clearTargetedMessage: clearSelectedMessage,
copyMessageText,
doubleCheckMissingQuoteReference,
dragAttachment,
kickOffAttachmentDownload,
markAttachmentAsCorrupted,
messageExpanded,
Expand Down Expand Up @@ -300,6 +301,7 @@ export const SmartTimelineItem = memo(function SmartTimelineItem(
sendPollVote={sendPollVote}
renderItem={renderItem}
returnToActiveCall={returnToActiveCall}
dragAttachment={dragAttachment}
saveAttachment={saveAttachment}
saveAttachments={saveAttachments}
scrollToPollMessage={scrollToPollMessage}
Expand Down