Display attachments from disk
This commit is contained in:
parent
407c77395b
commit
e1b620602d
6 changed files with 70 additions and 35 deletions
|
@ -180,6 +180,7 @@
|
||||||
Signal.Types.AttachmentTS.save({
|
Signal.Types.AttachmentTS.save({
|
||||||
attachment: this.model,
|
attachment: this.model,
|
||||||
document,
|
document,
|
||||||
|
getAbsolutePath: Signal.Migrations.getAbsoluteAttachmentPath,
|
||||||
timestamp: this.timestamp,
|
timestamp: this.timestamp,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -282,8 +282,8 @@
|
||||||
if (this.quoteView) {
|
if (this.quoteView) {
|
||||||
this.quoteView.remove();
|
this.quoteView.remove();
|
||||||
}
|
}
|
||||||
if (this.lightboxView) {
|
if (this.lightboxGalleryView) {
|
||||||
this.lightboxView.remove();
|
this.lightboxGalleryView.remove();
|
||||||
}
|
}
|
||||||
if (this.panels && this.panels.length) {
|
if (this.panels && this.panels.length) {
|
||||||
for (let i = 0, max = this.panels.length; i < max; i += 1) {
|
for (let i = 0, max = this.panels.length; i < max; i += 1) {
|
||||||
|
@ -598,6 +598,7 @@
|
||||||
WhisperMessageCollection,
|
WhisperMessageCollection,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// NOTE: Could we show grid previews from disk as well?
|
||||||
const loadMessages = Signal.Components.Types.Message
|
const loadMessages = Signal.Components.Types.Message
|
||||||
.loadWithObjectURL(Signal.Migrations.loadMessage);
|
.loadWithObjectURL(Signal.Migrations.loadMessage);
|
||||||
const media = await loadMessages(rawMedia);
|
const media = await loadMessages(rawMedia);
|
||||||
|
@ -605,30 +606,36 @@
|
||||||
const saveAttachment = async ({ message } = {}) => {
|
const saveAttachment = async ({ message } = {}) => {
|
||||||
const attachment = message.attachments[0];
|
const attachment = message.attachments[0];
|
||||||
const timestamp = message.received_at;
|
const timestamp = message.received_at;
|
||||||
Signal.Types.AttachmentTS.save({ attachment, timestamp });
|
Signal.Types.AttachmentTS.save({
|
||||||
|
attachment,
|
||||||
|
document,
|
||||||
|
getAbsolutePath: Signal.Migrations.getAbsoluteAttachmentPath,
|
||||||
|
timestamp,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onItemClick = async ({ message, type }) => {
|
const onItemClick = async ({ message, type }) => {
|
||||||
const loadedMessage = Signal.Components.Types.Message
|
|
||||||
.withObjectURL(await Signal.Migrations.loadMessage(message));
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'documents': {
|
case 'documents': {
|
||||||
saveAttachment({ message: loadedMessage });
|
saveAttachment({ message });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'media': {
|
case 'media': {
|
||||||
const attachment = loadedMessage.attachments[0];
|
const selectedIndex = media.findIndex(mediaMessage =>
|
||||||
this.lightboxView = new Whisper.ReactWrapperView({
|
mediaMessage.id === message.id);
|
||||||
Component: Signal.Components.Lightbox,
|
const { getAbsoluteAttachmentPath } = Signal.Migrations;
|
||||||
|
this.lightboxGalleryView = new Whisper.ReactWrapperView({
|
||||||
|
Component: Signal.Components.LightboxGallery,
|
||||||
props: {
|
props: {
|
||||||
objectURL: loadedMessage.objectURL,
|
getAbsoluteAttachmentPath,
|
||||||
contentType: attachment.contentType,
|
messages: media,
|
||||||
onSave: () => saveAttachment({ message: loadedMessage }),
|
onSave: () => saveAttachment({ message }),
|
||||||
|
selectedIndex,
|
||||||
},
|
},
|
||||||
onClose: () => Signal.Backbone.Views.Lightbox.hide(),
|
onClose: () => Signal.Backbone.Views.Lightbox.hide(),
|
||||||
});
|
});
|
||||||
Signal.Backbone.Views.Lightbox.show(this.lightboxView.el);
|
Signal.Backbone.Views.Lightbox.show(this.lightboxGalleryView.el);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -166,6 +166,7 @@ window.Signal.Logs = require('./js/modules/logs');
|
||||||
|
|
||||||
// React components
|
// React components
|
||||||
const { Lightbox } = require('./ts/components/Lightbox');
|
const { Lightbox } = require('./ts/components/Lightbox');
|
||||||
|
const { LightboxGallery } = require('./ts/components/LightboxGallery');
|
||||||
const { MediaGallery } =
|
const { MediaGallery } =
|
||||||
require('./ts/components/conversation/media-gallery/MediaGallery');
|
require('./ts/components/conversation/media-gallery/MediaGallery');
|
||||||
const { Quote } = require('./ts/components/conversation/Quote');
|
const { Quote } = require('./ts/components/conversation/Quote');
|
||||||
|
@ -175,6 +176,7 @@ const MediaGalleryMessage =
|
||||||
|
|
||||||
window.Signal.Components = {
|
window.Signal.Components = {
|
||||||
Lightbox,
|
Lightbox,
|
||||||
|
LightboxGallery,
|
||||||
MediaGallery,
|
MediaGallery,
|
||||||
Types: {
|
Types: {
|
||||||
Message: MediaGalleryMessage,
|
Message: MediaGalleryMessage,
|
||||||
|
|
|
@ -117,7 +117,7 @@ export class Lightbox extends React.Component<Props, {}> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const { contentType, objectURL } = this.props;
|
const { contentType, objectURL, onNext, onPrevious, onSave } = this.props;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={styles.container}
|
style={styles.container}
|
||||||
|
@ -132,23 +132,23 @@ export class Lightbox extends React.Component<Props, {}> {
|
||||||
</div>
|
</div>
|
||||||
<div style={styles.controls}>
|
<div style={styles.controls}>
|
||||||
<IconButton type="close" onClick={this.onClose} />
|
<IconButton type="close" onClick={this.onClose} />
|
||||||
{this.props.onSave ? (
|
{onSave ? (
|
||||||
<IconButton
|
<IconButton
|
||||||
type="save"
|
type="save"
|
||||||
onClick={this.props.onSave}
|
onClick={onSave}
|
||||||
style={styles.saveButton}
|
style={styles.saveButton}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={styles.navigationContainer}>
|
<div style={styles.navigationContainer}>
|
||||||
{this.props.onPrevious ? (
|
{onPrevious ? (
|
||||||
<IconButton type="previous" onClick={this.props.onPrevious} />
|
<IconButton type="previous" onClick={onPrevious} />
|
||||||
) : (
|
) : (
|
||||||
<IconButtonPlaceholder />
|
<IconButtonPlaceholder />
|
||||||
)}
|
)}
|
||||||
{this.props.onNext ? (
|
{onNext ? (
|
||||||
<IconButton type="next" onClick={this.props.onNext} />
|
<IconButton type="next" onClick={onNext} />
|
||||||
) : (
|
) : (
|
||||||
<IconButtonPlaceholder />
|
<IconButtonPlaceholder />
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -5,18 +5,18 @@ import React from 'react';
|
||||||
|
|
||||||
import * as MIME from '../types/MIME';
|
import * as MIME from '../types/MIME';
|
||||||
import { Lightbox } from './Lightbox';
|
import { Lightbox } from './Lightbox';
|
||||||
|
import { Message } from './conversation/media-gallery/types/Message';
|
||||||
|
|
||||||
interface Item {
|
interface Item {
|
||||||
objectURL: string;
|
objectURL?: string;
|
||||||
contentType: MIME.MIMEType | undefined;
|
contentType: MIME.MIMEType | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
close: () => void;
|
close: () => void;
|
||||||
items: Array<Item>;
|
getAbsoluteAttachmentPath: (relativePath: string) => string;
|
||||||
// onNext?: () => void;
|
messages: Array<Message>;
|
||||||
// onPrevious?: () => void;
|
onSave?: ({ message }: { message: Message }) => void;
|
||||||
onSave?: () => void;
|
|
||||||
selectedIndex: number;
|
selectedIndex: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,6 +24,11 @@ interface State {
|
||||||
selectedIndex: number;
|
selectedIndex: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const messageToItem = (message: Message): Item => ({
|
||||||
|
objectURL: message.attachments[0].path,
|
||||||
|
contentType: message.attachments[0].contentType,
|
||||||
|
});
|
||||||
|
|
||||||
export class LightboxGallery extends React.Component<Props, State> {
|
export class LightboxGallery extends React.Component<Props, State> {
|
||||||
public static defaultProps: Partial<Props> = {
|
public static defaultProps: Partial<Props> = {
|
||||||
selectedIndex: 0,
|
selectedIndex: 0,
|
||||||
|
@ -38,25 +43,30 @@ export class LightboxGallery extends React.Component<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const { close, items, onSave } = this.props;
|
const { close, getAbsoluteAttachmentPath, messages, onSave } = this.props;
|
||||||
const { selectedIndex } = this.state;
|
const { selectedIndex } = this.state;
|
||||||
|
|
||||||
const selectedItem: Item = items[selectedIndex];
|
const selectedMessage: Message = messages[selectedIndex];
|
||||||
|
const selectedItem = messageToItem(selectedMessage);
|
||||||
|
|
||||||
const firstIndex = 0;
|
const firstIndex = 0;
|
||||||
const onPrevious =
|
const onPrevious =
|
||||||
selectedIndex > firstIndex ? this.handlePrevious : undefined;
|
selectedIndex > firstIndex ? this.handlePrevious : undefined;
|
||||||
|
|
||||||
const lastIndex = items.length - 1;
|
const lastIndex = messages.length - 1;
|
||||||
const onNext = selectedIndex < lastIndex ? this.handleNext : undefined;
|
const onNext = selectedIndex < lastIndex ? this.handleNext : undefined;
|
||||||
|
|
||||||
|
const objectURL = selectedItem.objectURL
|
||||||
|
? getAbsoluteAttachmentPath(selectedItem.objectURL)
|
||||||
|
: 'images/video.svg';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Lightbox
|
<Lightbox
|
||||||
close={close}
|
close={close}
|
||||||
onPrevious={onPrevious}
|
onPrevious={onPrevious}
|
||||||
onNext={onNext}
|
onNext={onNext}
|
||||||
onSave={onSave}
|
onSave={onSave ? this.handleSave : undefined}
|
||||||
objectURL={selectedItem.objectURL}
|
objectURL={objectURL}
|
||||||
contentType={selectedItem.contentType}
|
contentType={selectedItem.contentType}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -72,8 +82,19 @@ export class LightboxGallery extends React.Component<Props, State> {
|
||||||
this.setState((prevState, props) => ({
|
this.setState((prevState, props) => ({
|
||||||
selectedIndex: Math.min(
|
selectedIndex: Math.min(
|
||||||
prevState.selectedIndex + 1,
|
prevState.selectedIndex + 1,
|
||||||
props.items.length - 1
|
props.messages.length - 1
|
||||||
),
|
),
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private handleSave = () => {
|
||||||
|
const { messages, onSave } = this.props;
|
||||||
|
if (!onSave) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { selectedIndex } = this.state;
|
||||||
|
const message = messages[selectedIndex];
|
||||||
|
onSave({ message });
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,16 +47,20 @@ export const isVisualMedia = (attachment: Attachment): boolean => {
|
||||||
export const save = ({
|
export const save = ({
|
||||||
attachment,
|
attachment,
|
||||||
document,
|
document,
|
||||||
|
getAbsolutePath,
|
||||||
timestamp,
|
timestamp,
|
||||||
}: {
|
}: {
|
||||||
attachment: Attachment;
|
attachment: Attachment;
|
||||||
document: Document;
|
document: Document;
|
||||||
|
getAbsolutePath: (relativePath: string) => string;
|
||||||
timestamp?: number;
|
timestamp?: number;
|
||||||
}): void => {
|
}): void => {
|
||||||
const url = arrayBufferToObjectURL({
|
const url = !is.undefined(attachment.path)
|
||||||
data: attachment.data,
|
? getAbsolutePath(attachment.path)
|
||||||
type: SAVE_CONTENT_TYPE,
|
: arrayBufferToObjectURL({
|
||||||
});
|
data: attachment.data,
|
||||||
|
type: SAVE_CONTENT_TYPE,
|
||||||
|
});
|
||||||
const filename = getSuggestedFilename({ attachment, timestamp });
|
const filename = getSuggestedFilename({ attachment, timestamp });
|
||||||
saveURLAsFile({ url, filename, document });
|
saveURLAsFile({ url, filename, document });
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue