Add back ability to edit caption before sending attachments

pull/1381/head
Audric Ackermann 4 years ago
parent f673589c56
commit 2ec337dd31
No known key found for this signature in database
GPG Key ID: 999F434D76324AD4

@ -1097,6 +1097,10 @@
"description": "Used as a 'commit changes' button in the Caption Editor for outgoing image attachments", "description": "Used as a 'commit changes' button in the Caption Editor for outgoing image attachments",
"androidKey": "media_preview__save_title" "androidKey": "media_preview__save_title"
}, },
"saved": {
"message": "Saved",
"description": "Used to display when an action was saved."
},
"emojiAlt": { "emojiAlt": {
"message": "Emoji image of '$title$'", "message": "Emoji image of '$title$'",
"description": "Used in the alt tag of all emoji images", "description": "Used in the alt tag of all emoji images",

@ -18,10 +18,6 @@ const AttachmentDownloads = require('./attachment_downloads');
const { const {
ConversationLoadingScreen, ConversationLoadingScreen,
} = require('../../ts/components/ConversationLoadingScreen'); } = require('../../ts/components/ConversationLoadingScreen');
const {
AttachmentList,
} = require('../../ts/components/conversation/AttachmentList');
const { CaptionEditor } = require('../../ts/components/CaptionEditor');
const { const {
ContactDetail, ContactDetail,
} = require('../../ts/components/conversation/ContactDetail'); } = require('../../ts/components/conversation/ContactDetail');
@ -231,8 +227,6 @@ exports.setup = (options = {}) => {
const Components = { const Components = {
ConversationLoadingScreen, ConversationLoadingScreen,
AttachmentList,
CaptionEditor,
ContactDetail, ContactDetail,
ContactListItem, ContactListItem,
ContactName, ContactName,

@ -377,6 +377,7 @@ setInterval(() => {
const { autoOrientImage } = require('./js/modules/auto_orient_image'); const { autoOrientImage } = require('./js/modules/auto_orient_image');
window.autoOrientImage = autoOrientImage; window.autoOrientImage = autoOrientImage;
window.loadImage = require('blueimp-load-image');
window.dataURLToBlobSync = require('blueimp-canvas-to-blob'); window.dataURLToBlobSync = require('blueimp-canvas-to-blob');
window.filesize = require('filesize'); window.filesize = require('filesize');
window.getGuid = require('uuid/v4'); window.getGuid = require('uuid/v4');

@ -273,7 +273,6 @@
input { input {
height: 38px; height: 38px;
width: 142px;
border-radius: 5px; border-radius: 5px;
text-align: center; text-align: center;
font-size: $session-font-md; font-size: $session-font-md;

@ -2155,7 +2155,9 @@
// Module: Caption Editor // Module: Caption Editor
.module-caption-editor { .module-caption-editor {
background-color: $color-black; @include themify($themes) {
background-color: themed('inboxBackground');
}
z-index: 20; z-index: 20;
position: absolute; position: absolute;
@ -2179,13 +2181,14 @@
width: 30px; width: 30px;
height: 30px; height: 30px;
z-index: 2; z-index: 2;
@include color-svg('../images/x-16.svg', $color-white); @include themify($themes) {
@include color-svg('../images/x-16.svg', themed('textColor'));
}
} }
.module-caption-editor__media-container { .module-caption-editor__media-container {
flex-grow: 1; flex-grow: 1;
flex-shrink: 1; flex-shrink: 1;
background-color: $color-black;
text-align: center; text-align: center;
margin: 50px; margin: 50px;
overflow: hidden; overflow: hidden;
@ -2221,58 +2224,49 @@
flex-grow: 0; flex-grow: 0;
flex-shrink: 0; flex-shrink: 0;
height: 52px; height: 52px;
width: 100%;
padding: 8px; padding: 8px;
justify-content: center;
display: inline-flex; display: inline-flex;
flex-direction: row; flex-direction: row;
align-items: middle;
margin-inline-start: auto; margin-inline-start: auto;
margin-inline-end: auto; margin-inline-end: auto;
} }
.module-caption-editor__input-container { .module-caption-editor__input-container {
position: relative; display: flex;
}
.module-caption-editor {
.session-button {
margin-inline-start: 15px;
}
} }
.module-caption-editor__caption-input { .module-caption-editor__caption-input {
height: 36px; height: 36px;
width: 40em;
font-size: 14px; font-size: 14px;
color: $color-white;
border: 1px solid $color-white; @include themify($themes) {
color: themed('textColor');
border: 1px solid themed('textColor');
background-color: themed('inputBackground');
}
border-radius: 18px; border-radius: 18px;
background-color: $color-black;
padding: 9px; padding: 9px;
padding-inline-start: 12px; padding-inline-start: 12px;
padding-inline-end: 65px; padding-inline-end: 65px;
&::placeholder { &::placeholder {
color: $color-white-07; color: $color-white-07;
@include themify($themes) {
color: subtle(themed('textColor'));
border: 1px solid themed('textColor');
background-color: themed('inputBackground');
}
} }
&:focus {
border: 1px solid $color-loki-green;
outline: none;
}
}
.module-caption-editor__save-button {
position: absolute;
background-color: $color-loki-green;
color: $color-white;
cursor: pointer;
height: 28px;
border-radius: 15px;
padding: 5px;
padding-inline-start: 12px;
padding-inline-end: 12px;
right: 4px;
top: 4px;
} }
// Module: Staged Placeholder Attachment // Module: Staged Placeholder Attachment

@ -774,11 +774,6 @@
} }
} }
.module-caption-editor__save-button {
background-color: $color-loki-green;
color: $color-white;
}
// Module: Search Results // Module: Search Results
.module-search-results__conversations-header { .module-search-results__conversations-header {

@ -5,15 +5,19 @@ import * as GoogleChrome from '../util/GoogleChrome';
import { AttachmentType } from '../types/Attachment'; import { AttachmentType } from '../types/Attachment';
import { LocalizerType } from '../types/Util'; import { SessionInput } from './session/SessionInput';
import {
SessionButton,
SessionButtonColor,
SessionButtonType,
} from './session/SessionButton';
interface Props { interface Props {
attachment: AttachmentType; attachment: AttachmentType;
i18n: LocalizerType;
url: string; url: string;
caption?: string; caption?: string;
onSave?: (caption: string) => void; onSave: (caption: string) => void;
close?: () => void; onClose: () => void;
} }
interface State { interface State {
@ -21,15 +25,7 @@ interface State {
} }
export class CaptionEditor extends React.Component<Props, State> { export class CaptionEditor extends React.Component<Props, State> {
private readonly handleKeyUpBound: ( private readonly inputRef: React.RefObject<any>;
event: React.KeyboardEvent<HTMLInputElement>
) => void;
private readonly setFocusBound: () => void;
private readonly onChangeBound: (
event: React.FormEvent<HTMLInputElement>
) => void;
private readonly onSaveBound: () => void;
private readonly inputRef: React.RefObject<HTMLInputElement>;
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
@ -38,60 +34,26 @@ export class CaptionEditor extends React.Component<Props, State> {
this.state = { this.state = {
caption: caption || '', caption: caption || '',
}; };
this.onSave = this.onSave.bind(this);
this.handleKeyUpBound = this.handleKeyUp.bind(this); this.onChange = this.onChange.bind(this);
this.setFocusBound = this.setFocus.bind(this);
this.onChangeBound = this.onChange.bind(this);
this.onSaveBound = this.onSave.bind(this);
this.inputRef = React.createRef(); this.inputRef = React.createRef();
} }
public componentDidMount() {
// Forcing focus after a delay due to some focus contention with ConversationView
setTimeout(() => {
this.setFocus();
}, 200);
}
public handleKeyUp(event: React.KeyboardEvent<HTMLInputElement>) {
const { close, onSave } = this.props;
if (close && event.key === 'Escape') {
close();
}
if (onSave && event.key === 'Enter') {
const { caption } = this.state;
onSave(caption);
}
}
public setFocus() {
if (this.inputRef.current) {
this.inputRef.current.focus();
}
}
public onSave() { public onSave() {
const { onSave } = this.props; const { onSave } = this.props;
const { caption } = this.state; const { caption } = this.state;
if (onSave) { onSave(caption);
onSave(caption);
}
} }
public onChange(event: React.FormEvent<HTMLInputElement>) { public onChange(value: string) {
// @ts-ignore
const { value } = event.target;
this.setState({ this.setState({
caption: value, caption: value,
}); });
} }
public renderObject() { public renderObject() {
const { url, i18n, attachment } = this.props; const { url, attachment } = this.props;
const { contentType } = attachment || { contentType: null }; const { contentType } = attachment || { contentType: null };
const isImageTypeSupported = GoogleChrome.isImageTypeSupported(contentType); const isImageTypeSupported = GoogleChrome.isImageTypeSupported(contentType);
@ -99,7 +61,7 @@ export class CaptionEditor extends React.Component<Props, State> {
return ( return (
<img <img
className="module-caption-editor__image" className="module-caption-editor__image"
alt={i18n('imageAttachmentAlt')} alt={window.i18n('imageAttachmentAlt')}
src={url} src={url}
/> />
); );
@ -118,19 +80,14 @@ export class CaptionEditor extends React.Component<Props, State> {
} }
public render() { public render() {
const { i18n, close } = this.props; const { onClose } = this.props;
const { caption } = this.state; const { caption } = this.state;
const onKeyUp = close ? this.handleKeyUpBound : undefined;
return ( return (
<div <div role="dialog" className="module-caption-editor">
role="dialog"
onClick={this.setFocusBound}
className="module-caption-editor"
>
<div <div
role="button" role="button"
onClick={close} onClick={onClose}
className="module-caption-editor__close-button" className="module-caption-editor__close-button"
/> />
<div className="module-caption-editor__media-container"> <div className="module-caption-editor__media-container">
@ -138,24 +95,24 @@ export class CaptionEditor extends React.Component<Props, State> {
</div> </div>
<div className="module-caption-editor__bottom-bar"> <div className="module-caption-editor__bottom-bar">
<div className="module-caption-editor__input-container"> <div className="module-caption-editor__input-container">
<input <SessionInput
type="text" type="text"
autoFocus={true}
maxLength={200}
ref={this.inputRef} ref={this.inputRef}
placeholder={window.i18n('addACaption')}
enableShowHide={false}
onValueChanged={this.onChange}
onEnterPressed={this.onSave}
value={caption} value={caption}
maxLength={200}
placeholder={i18n('addACaption')}
className="module-caption-editor__caption-input"
onKeyUp={onKeyUp}
onChange={this.onChangeBound}
/> />
{caption ? ( {caption ? (
<div <SessionButton
role="button" text={window.i18n('save')}
onClick={this.onSaveBound} onClick={this.onSave}
className="module-caption-editor__save-button" buttonType={SessionButtonType.Brand}
> buttonColor={SessionButtonColor.Green}
{i18n('save')} />
</div>
) : null} ) : null}
</div> </div>
</div> </div>

@ -66,11 +66,16 @@ const styles = {
display: 'inline-flex', display: 'inline-flex',
justifyContent: 'center', justifyContent: 'center',
} as React.CSSProperties, } as React.CSSProperties,
objectParentContainer: {
flexGrow: 1,
textAlign: 'center' as 'center',
margin: 'auto',
},
object: { object: {
flexGrow: 1, flexGrow: 1,
flexShrink: 0, flexShrink: 0,
maxWidth: '100%', maxWidth: '80vw',
maxHeight: '100%', maxHeight: '80vh',
objectFit: 'contain', objectFit: 'contain',
} as React.CSSProperties, } as React.CSSProperties,
caption: { caption: {
@ -79,11 +84,11 @@ const styles = {
left: 0, left: 0,
right: 0, right: 0,
textAlign: 'center', textAlign: 'center',
color: 'white', color: 'black',
padding: '1em', padding: '1em',
paddingLeft: '3em', paddingLeft: '3em',
paddingRight: '3em', paddingRight: '3em',
backgroundColor: 'rgba(192, 192, 192, .20)', backgroundColor: 'rgba(192, 192, 192, .40)',
} as React.CSSProperties, } as React.CSSProperties,
controlsOffsetPlaceholder: { controlsOffsetPlaceholder: {
width: CONTROLS_WIDTH, width: CONTROLS_WIDTH,
@ -229,11 +234,13 @@ export class Lightbox extends React.Component<Props> {
> >
<div style={styles.mainContainer}> <div style={styles.mainContainer}>
<div style={styles.controlsOffsetPlaceholder} /> <div style={styles.controlsOffsetPlaceholder} />
<div style={styles.objectContainer}> <div style={styles.objectParentContainer}>
{!is.undefined(contentType) <div style={styles.objectContainer}>
? this.renderObject({ objectURL, contentType }) {!is.undefined(contentType)
: null} ? this.renderObject({ objectURL, contentType })
{caption ? <div style={styles.caption}>{caption}</div> : null} : null}
{caption ? <div style={styles.caption}>{caption}</div> : null}
</div>
</div> </div>
<div style={styles.controls}> <div style={styles.controls}>
<IconButton type="close" onClick={this.onClose} /> <IconButton type="close" onClick={this.onClose} />

@ -4,16 +4,17 @@ import classNames from 'classnames';
import { SessionIconButton, SessionIconSize, SessionIconType } from './icon'; import { SessionIconButton, SessionIconSize, SessionIconType } from './icon';
interface Props { interface Props {
label: string; label?: string;
error?: string; error?: string;
type: string; type?: string;
value?: string; value?: string;
placeholder: string; placeholder: string;
maxLength?: number; maxLength?: number;
enableShowHide?: boolean; enableShowHide?: boolean;
onValueChanged?: any; onValueChanged?: (value: string) => any;
onEnterPressed?: any; onEnterPressed?: any;
autoFocus?: boolean; autoFocus?: boolean;
ref?: any;
} }
interface State { interface State {

@ -26,6 +26,7 @@ import { AbortController } from 'abort-controller';
import { SessionQuotedMessageComposition } from './SessionQuotedMessageComposition'; import { SessionQuotedMessageComposition } from './SessionQuotedMessageComposition';
import { Mention, MentionsInput } from 'react-mentions'; import { Mention, MentionsInput } from 'react-mentions';
import { MemberItem } from '../../conversation/MemberList'; import { MemberItem } from '../../conversation/MemberList';
import { CaptionEditor } from '../../CaptionEditor';
export interface ReplyingToMessageProps { export interface ReplyingToMessageProps {
convoId: string; convoId: string;
@ -83,6 +84,7 @@ interface State {
voiceRecording?: Blob; voiceRecording?: Blob;
ignoredLink?: string; // set the the ignored url when users closed the link preview ignoredLink?: string; // set the the ignored url when users closed the link preview
stagedLinkPreview?: StagedLinkPreviewData; stagedLinkPreview?: StagedLinkPreviewData;
showCaptionEditor?: AttachmentType;
} }
const sendMessageStyle = { const sendMessageStyle = {
@ -156,6 +158,8 @@ export class SessionCompositionBox extends React.Component<Props, State> {
// Attachments // Attachments
this.onChoseAttachment = this.onChoseAttachment.bind(this); this.onChoseAttachment = this.onChoseAttachment.bind(this);
this.onChooseAttachment = this.onChooseAttachment.bind(this); this.onChooseAttachment = this.onChooseAttachment.bind(this);
this.onClickAttachment = this.onClickAttachment.bind(this);
this.renderCaptionEditor = this.renderCaptionEditor.bind(this);
// On Sending // On Sending
this.onSendMessage = this.onSendMessage.bind(this); this.onSendMessage = this.onSendMessage.bind(this);
@ -451,7 +455,7 @@ export class SessionCompositionBox extends React.Component<Props, State> {
if (!conversationModel) { if (!conversationModel) {
return; return;
} }
const allPubKeys = conversationModel.get('members'); const allPubKeys = conversationModel.get('members') as Array<string>;
const allMembers = allPubKeys.map(pubKey => { const allMembers = allPubKeys.map(pubKey => {
const conv = window.ConversationController.get(pubKey); const conv = window.ConversationController.get(pubKey);
@ -619,18 +623,70 @@ export class SessionCompositionBox extends React.Component<Props, State> {
return <></>; return <></>;
} }
private onClickAttachment(attachment: AttachmentType) {
// const onSave = (caption: string) => {
// // eslint-disable-next-line no-param-reassign
// attachment.caption = caption;
// // this.captionEditorView.remove();
// // Signal.Backbone.Views.Lightbox.hide();
// this.render();
// };
this.setState({ showCaptionEditor: attachment });
// this.captionEditorView = new Whisper.ReactWrapperView({
// className: 'attachment-list-wrapper',
// Component: window.Signal.Components.CaptionEditor,
// props: getProps(),
// onClose: () => Signal.Backbone.Views.Lightbox.hide(),
// });
// Signal.Backbone.Views.Lightbox.show(this.captionEditorView.el);
}
private renderCaptionEditor(attachment?: AttachmentType) {
if (attachment) {
const onSave = (caption: string) => {
// eslint-disable-next-line no-param-reassign
attachment.caption = caption;
ToastUtils.pushToastInfo('saved', window.i18n('saved'));
// close the lightbox on save
this.setState({
showCaptionEditor: undefined,
});
};
const url = attachment.videoUrl || attachment.url;
return (
<CaptionEditor
attachment={attachment}
url={url}
onSave={onSave}
caption={attachment.caption}
onClose={() => {
this.setState({
showCaptionEditor: undefined,
});
}}
/>
);
}
return <></>;
}
private renderAttachmentsStaged() { private renderAttachmentsStaged() {
const { stagedAttachments } = this.props; const { stagedAttachments } = this.props;
const { showCaptionEditor } = this.state;
if (stagedAttachments && stagedAttachments.length) { if (stagedAttachments && stagedAttachments.length) {
return ( return (
<AttachmentList <>
attachments={stagedAttachments} <AttachmentList
// tslint:disable-next-line: no-empty attachments={stagedAttachments}
onClickAttachment={() => {}} onClickAttachment={this.onClickAttachment}
onAddAttachment={this.onChooseAttachment} onAddAttachment={this.onChooseAttachment}
onCloseAttachment={this.props.removeAttachment} onCloseAttachment={this.props.removeAttachment}
onClose={this.props.clearAttachments} onClose={this.props.clearAttachments}
/> />
{this.renderCaptionEditor(showCaptionEditor)}
</>
); );
} }
return <></>; return <></>;

@ -40,7 +40,10 @@ export async function autoScale<T extends { contentType: string; file: any }>(
return; return;
} }
if (file.type === 'image/gif' && file.size <= Constants.CONVERSATION.MAX_ATTACHMENT_FILESIZE_BYTES) { if (
file.type === 'image/gif' &&
file.size <= Constants.CONVERSATION.MAX_ATTACHMENT_FILESIZE_BYTES
) {
resolve(attachment); resolve(attachment);
return; return;
} }
@ -68,15 +71,12 @@ export async function autoScale<T extends { contentType: string; file: any }>(
if (quality > 1) { if (quality > 1) {
quality = 0.95; quality = 0.95;
} }
} while (i > 0 && blob.size > maxSize); } while (i > 0 && blob.size > maxSize);
resolve({ resolve({
...attachment, ...attachment,
file: blob, file: blob,
}); });
}; };
img.src = url; img.src = url;
}); });

Loading…
Cancel
Save