Emoji panel init

pull/1102/head
Vincent 5 years ago
parent 19a5571536
commit 6c2043753f

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

@ -218,7 +218,7 @@ message DataMessage {
optional Quote quote = 8; optional Quote quote = 8;
repeated Contact contact = 9; repeated Contact contact = 9;
repeated Preview preview = 10; repeated Preview preview = 10;
optional LokiProfile profile = 101; // Loki: The profile of the current user optional LokiProfile profile = 101; // Loki: The profile of the current user
optional GroupInvitation groupInvitation = 102; // Loki: Invitation to a public chat optional GroupInvitation groupInvitation = 102; // Loki: Invitation to a public chat
} }

@ -43,6 +43,8 @@
} }
} }
@import 'node_modules/emoji-mart/css/emoji-mart.css';
// Session Colors // Session Colors
$session-font-family: 'Wasa'; $session-font-family: 'Wasa';
@ -52,12 +54,13 @@ $session-color-green-alt-2: #00fd73;
$session-color-green-alt-3: #00f782; $session-color-green-alt-3: #00f782;
$session-shade-1: #0c0c0c; $session-shade-1: #0c0c0c;
$session-shade-1-alt: #0F1011; $session-shade-1-alt: #0f1011;
$session-shade-2: #161616; $session-shade-2: #161616;
$session-shade-3: #191818; $session-shade-3: #191818;
$session-shade-4: #1b1b1b; $session-shade-4: #1b1b1b;
$session-shade-5: #222325; $session-shade-5: #222325;
$session-shade-6: #232323; $session-shade-6: #232323;
$session-shade-6-alt: #2c2c2c;
$session-shade-7: #2e2e2e; $session-shade-7: #2e2e2e;
$session-shade-8: #2f2f2f; $session-shade-8: #2f2f2f;
$session-shade-9: #313131; $session-shade-9: #313131;
@ -79,7 +82,7 @@ $session-color-white: #fff;
$session-color-dark-grey: #353535; $session-color-dark-grey: #353535;
$session-color-black: #000; $session-color-black: #000;
$session-color-danger: #ff453a; $session-color-danger: #ff453a;
$session-color-danger-alt: #FF4538; $session-color-danger-alt: #ff4538;
$session-color-primary: $session-shade-13; $session-color-primary: $session-shade-13;
$session-color-secondary: $session-shade-6; $session-color-secondary: $session-shade-6;
$session-background-overlay: #212121; $session-background-overlay: #212121;
@ -590,7 +593,6 @@ label {
} }
} }
.hidden { .hidden {
display: none; display: none;
visibility: hidden; visibility: hidden;

@ -1,8 +1,5 @@
$composition-container-height: 60px; $composition-container-height: 60px;
@keyframes fadein { @keyframes fadein {
from { from {
opacity: 0; opacity: 0;
@ -41,23 +38,22 @@ $composition-container-height: 60px;
} }
} }
.conversation-item { .conversation-item {
display: flex; display: flex;
flex-grow: 1; flex-grow: 1;
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
outline: none; outline: none;
.selection-mode { .selection-mode {
.messages-container > *:not(.message-selected) { .messages-container > *:not(.message-selected) {
animation: toShadow $session-transition-duration; animation: toShadow $session-transition-duration;
opacity: 0.25; opacity: 0.25;
} }
.conversation-header{ .conversation-header {
.conversation-header{ .conversation-header {
&--items-wrapper{ &--items-wrapper {
.session-icon { .session-icon {
opacity: 0; opacity: 0;
} }
@ -71,9 +67,8 @@ $composition-container-height: 60px;
} }
} }
.conversation-header { .conversation-header {
&--items-wrapper{ &--items-wrapper {
display: flex; display: flex;
flex-grow: 1; flex-grow: 1;
align-items: center; align-items: center;
@ -89,11 +84,11 @@ $composition-container-height: 60px;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
height: $main-view-header-height; height: $main-view-header-height;
.close-button { .close-button {
float: left; float: left;
} }
.session-button.default.danger { .session-button.default.danger {
display: flex; display: flex;
width: 80px; width: 80px;
@ -102,15 +97,12 @@ $composition-container-height: 60px;
.message-selection-overlay div[role='button'] { .message-selection-overlay div[role='button'] {
display: inline-block; display: inline-block;
} }
.message-selection-overlay .button-group { .message-selection-overlay .button-group {
float: right; float: right;
} }
} }
.session-conversation-wrapper { .session-conversation-wrapper {
position: absolute; position: absolute;
width: 100%; width: 100%;
@ -124,8 +116,8 @@ $composition-container-height: 60px;
flex-direction: column; flex-direction: column;
position: relative; position: relative;
&--blocking-overlay{ &--blocking-overlay {
background-color: rgba(0,0,0,0.80); background-color: rgba(0, 0, 0, 0.8);
position: absolute; position: absolute;
top: 0px; top: 0px;
bottom: 0px; bottom: 0px;
@ -212,13 +204,37 @@ $composition-container-height: 60px;
position: absolute; position: absolute;
bottom: 68px; bottom: 68px;
right: 0px; right: 0px;
min-height: 400px;
min-width: 400px;
background-color: $session-shade-4;
border: 1px solid $session-shade-6;
border-top-right-radius: 3px;
border-top-left-radius: 3px;
padding: $session-margin-lg; padding: $session-margin-lg;
z-index: 5;
opacity: 0;
visibility: hidden;
transition: $session-transition-duration;
button:focus {
outline: none;
}
&.show {
opacity: 1;
visibility: visible;
}
& > section {
background-color: $session-shade-4;
border: 1px solid $session-shade-6-alt;
border-radius: $session-margin-lg;
.emoji-mart-category-label {
top: -2px;
span {
font-family: 'SF Pro Text';
padding-top: $session-margin-sm;
background-color: $session-shade-4;
}
}
}
} }
.session-progress { .session-progress {
@ -236,7 +252,6 @@ $composition-container-height: 60px;
} }
} }
.session-recording { .session-recording {
height: $composition-container-height; height: $composition-container-height;
display: flex; display: flex;
@ -282,7 +297,6 @@ $composition-container-height: 60px;
width: 100%; width: 100%;
padding: 0px $session-margin-lg; padding: 0px $session-margin-lg;
} }
} }
&--status { &--status {
@ -293,18 +307,18 @@ $composition-container-height: 60px;
right: 0; right: 0;
bottom: $composition-container-height + $session-margin-md; bottom: $composition-container-height + $session-margin-md;
.session-button{ .session-button {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
width: 173px; width: 173px;
font-weight: 300; font-weight: 300;
font-family: "SF Pro Text"; font-family: 'SF Pro Text';
&.primary { &.primary {
cursor: default; cursor: default;
user-select: none; user-select: none;
&:hover{ &:hover {
filter: brightness(100%); filter: brightness(100%);
border: 2px solid #161819; border: 2px solid #161819;
} }
@ -317,7 +331,7 @@ $composition-container-height: 60px;
&--timer { &--timer {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
font-family: "SF Pro Text"; font-family: 'SF Pro Text';
font-weight: bold; font-weight: bold;
font-size: 14px; font-size: 14px;
@ -325,7 +339,7 @@ $composition-container-height: 60px;
margin-right: $session-margin-sm; margin-right: $session-margin-sm;
} }
&-light{ &-light {
height: $session-margin-sm; height: $session-margin-sm;
width: $session-margin-sm; width: $session-margin-sm;
border-radius: 50%; border-radius: 50%;

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { debounce } from 'lodash';
import { Attachment } from '../../../types/Attachment'; import { Attachment } from '../../../types/Attachment';
import * as MIME from '../../../types/MIME'; import * as MIME from '../../../types/MIME';
@ -33,6 +34,7 @@ interface State {
export class SessionCompositionBox extends React.Component<Props, State> { export class SessionCompositionBox extends React.Component<Props, State> {
private textarea: React.RefObject<HTMLTextAreaElement>; private textarea: React.RefObject<HTMLTextAreaElement>;
private fileInput: React.RefObject<HTMLInputElement>; private fileInput: React.RefObject<HTMLInputElement>;
private emojiPanel: any;
constructor(props: any) { constructor(props: any) {
super(props); super(props);
@ -49,7 +51,12 @@ export class SessionCompositionBox extends React.Component<Props, State> {
this.textarea = React.createRef(); this.textarea = React.createRef();
this.fileInput = React.createRef(); this.fileInput = React.createRef();
this.toggleEmojiPanel = this.toggleEmojiPanel.bind(this); // Emojis
this.emojiPanel = null;
this.toggleEmojiPanel = debounce(this.toggleEmojiPanel.bind(this), 100);
this.hideEmojiPanel = this.hideEmojiPanel.bind(this);
this.onEmojiClick = this.onEmojiClick.bind(this);
this.handleClick = this.handleClick.bind(this);
this.renderRecordingView = this.renderRecordingView.bind(this); this.renderRecordingView = this.renderRecordingView.bind(this);
this.renderCompositionView = this.renderCompositionView.bind(this); this.renderCompositionView = this.renderCompositionView.bind(this);
@ -64,6 +71,7 @@ export class SessionCompositionBox extends React.Component<Props, State> {
this.onChooseAttachment = this.onChooseAttachment.bind(this); this.onChooseAttachment = this.onChooseAttachment.bind(this);
this.onKeyDown = this.onKeyDown.bind(this); this.onKeyDown = this.onKeyDown.bind(this);
this.onChange = this.onChange.bind(this);
} }
@ -76,7 +84,7 @@ export class SessionCompositionBox extends React.Component<Props, State> {
this.setState({mediaSetting}); this.setState({mediaSetting});
} }
render() { public render() {
const { isRecordingView } = this.state; const { isRecordingView } = this.state;
return ( return (
@ -90,11 +98,37 @@ export class SessionCompositionBox extends React.Component<Props, State> {
); );
} }
public toggleEmojiPanel() { private handleClick(e: any) {
if (this.emojiPanel && this.emojiPanel.contains(e.target)) {
return;
}
this.toggleEmojiPanel();
};
private showEmojiPanel() {
document.addEventListener('mousedown', this.handleClick, false);
this.setState({
showEmojiPanel: true,
});
}
private hideEmojiPanel() {
document.removeEventListener('mousedown', this.handleClick, false);
this.setState({ this.setState({
showEmojiPanel: !this.state.showEmojiPanel, showEmojiPanel: false,
}); });
} }
public toggleEmojiPanel() {
if (this.state.showEmojiPanel) {
this.hideEmojiPanel();
} else {
this.showEmojiPanel();
}
}
private renderRecordingView() { private renderRecordingView() {
return ( return (
@ -108,7 +142,7 @@ export class SessionCompositionBox extends React.Component<Props, State> {
private renderCompositionView() { private renderCompositionView() {
const { placeholder } = this.props; const { placeholder } = this.props;
const { showEmojiPanel } = this.state; const { showEmojiPanel, message } = this.state;
return ( return (
<> <>
@ -122,7 +156,7 @@ export class SessionCompositionBox extends React.Component<Props, State> {
className="hidden" className="hidden"
multiple={true} multiple={true}
ref={this.fileInput} ref={this.fileInput}
type='file' type="file"
onChange={this.onChoseAttachment} onChange={this.onChoseAttachment}
/> />
@ -140,6 +174,8 @@ export class SessionCompositionBox extends React.Component<Props, State> {
placeholder={placeholder} placeholder={placeholder}
maxLength={window.CONSTANTS.MAX_MESSAGE_BODY_LENGTH} maxLength={window.CONSTANTS.MAX_MESSAGE_BODY_LENGTH}
onKeyDown={this.onKeyDown} onKeyDown={this.onKeyDown}
value={message}
onChange={this.onChange}
/> />
</div> </div>
@ -158,11 +194,19 @@ export class SessionCompositionBox extends React.Component<Props, State> {
/> />
</div> </div>
{showEmojiPanel && <SessionEmojiPanel />} <div
ref={ref => (this.emojiPanel = ref)}
onKeyDown={this.onKeyDown}
role="button"
>
<SessionEmojiPanel onEmojiClicked={this.onEmojiClick} show={showEmojiPanel}/>
</div>
</> </>
); );
} }
private onChooseAttachment() { private onChooseAttachment() {
this.fileInput.current?.click(); this.fileInput.current?.click();
} }
@ -182,7 +226,7 @@ export class SessionCompositionBox extends React.Component<Props, State> {
fileName: file.name, fileName: file.name,
flags: undefined, flags: undefined,
// FIXME VINCE: Set appropriate type // FIXME VINCE: Set appropriate type
contentType: undefined, contentType: MIME.AUDIO_WEBM,
size: file.size, size: file.size,
data: fileBuffer, data: fileBuffer,
} }
@ -199,45 +243,47 @@ export class SessionCompositionBox extends React.Component<Props, State> {
// If shift, newline. Else send message. // If shift, newline. Else send message.
event.preventDefault(); event.preventDefault();
this.onSendMessage(); this.onSendMessage();
} else if (event.key === 'Escape' && this.state.showEmojiPanel) {
this.hideEmojiPanel();
} }
} }
private onDrop(){
// On drop attachments!
// this.textarea.current?.ondrop;
// Look into react-dropzone
}
private onSendMessage(){ private onSendMessage(){
// FIXME VINCE: Get emoiji, attachments, etc // FIXME VINCE: Get emoiji, attachments, etc
const messagePlaintext = this.textarea.current?.value; const messagePlaintext = this.textarea.current?.value;
const {attachments, voiceRecording} = this.state; const {attachments} = this.state;
const messageInput = this.textarea.current; const messageInput = this.textarea.current;
if (!messageInput) return; if (!messageInput) return;
console.log(`[vince][msg] Message:`, messagePlaintext); console.log(`[vince][msg] Message:`, messagePlaintext);
console.log(`[vince][msg] fileAttachments:`, attachments); console.log(`[vince][msg] fileAttachments:`, attachments);
console.log(`[vince][msg] Voice message:`, voiceRecording);
// Verify message length // Verify message length
// Handle emojis // Handle emojis
// Send message
const messageSuccess = this.props.sendMessage( const messageSuccess = this.props.sendMessage(
messagePlaintext, messagePlaintext,
attachments, attachments,
MIME.IMAGE_JPEG, undefined,
undefined, undefined,
null, null,
{}, {},
); );
if (messageSuccess) { if (messageSuccess) {
// Empty attachments
// Empty composition box // Empty composition box
messageInput.value = ''; this.setState({
message: '',
attachments: [],
});
} }
} }
@ -249,6 +295,7 @@ export class SessionCompositionBox extends React.Component<Props, State> {
const audioAttachment: Attachment = { const audioAttachment: Attachment = {
data: fileBuffer, data: fileBuffer,
flags: SignalService.AttachmentPointer.Flags.VOICE_MESSAGE, flags: SignalService.AttachmentPointer.Flags.VOICE_MESSAGE,
contentType: MIME.AUDIO_MP3,
}; };
const messageSuccess = this.props.sendMessage( const messageSuccess = this.props.sendMessage(
@ -295,5 +342,40 @@ export class SessionCompositionBox extends React.Component<Props, State> {
this.props.onExitVoiceNoteView(); this.props.onExitVoiceNoteView();
} }
private onDrop(){
// On drop attachments!
// this.textarea.current?.ondrop;
// Look into react-dropzone
}
private onChange(event: any) {
this.setState({message: event.target.value});
}
private onEmojiClick({native}: any) {
const messageBox = this.textarea.current;
if (!messageBox) return;
const { message } = this.state;
const currentSelectionStart = Number(messageBox.selectionStart);
const currentSelectionEnd = Number(messageBox.selectionEnd);
const before = message.slice(0, currentSelectionStart);
const end = message.slice(currentSelectionEnd);
const newMessage = `${before}${native}${end}`;
this.setState({ message: newMessage }, () => {
// update our selection because updating text programmatically
// will put the selection at the end of the textarea
const selectionStart = currentSelectionStart + Number(native.length);
messageBox.selectionStart = selectionStart;
messageBox.selectionEnd = selectionStart;
// Sometimes, we have to repeat the set of the selection position with a timeout to be effective
setTimeout(() => {
messageBox.selectionStart = selectionStart;
messageBox.selectionEnd = selectionStart;
}, 20);
});
}
} }

@ -100,7 +100,7 @@ export class SessionConversation extends React.Component<any, State> {
} }
} }
render() { public render() {
console.log(`[vince][info] Props`, this.props); console.log(`[vince][info] Props`, this.props);
const { messages, conversationKey, doneInitialScroll, isRecordingView } = this.state; const { messages, conversationKey, doneInitialScroll, isRecordingView } = this.state;

@ -1,6 +1,12 @@
import React from 'react'; import React from 'react';
import classNames from 'classnames';
interface Props {} import { Picker } from 'emoji-mart';
interface Props {
onEmojiClicked: (emoji: any) => void;
show: boolean;
}
interface State { interface State {
// FIXME Use Emoji-Mart categories // FIXME Use Emoji-Mart categories
@ -8,7 +14,7 @@ interface State {
} }
export class SessionEmojiPanel extends React.Component<Props, State> { export class SessionEmojiPanel extends React.Component<Props, State> {
constructor(props: any) { constructor(props: Props) {
super(props); super(props);
this.state = { this.state = {
@ -17,6 +23,23 @@ export class SessionEmojiPanel extends React.Component<Props, State> {
} }
render() { render() {
return <div className="session-emoji-panel">THIS IS EMOJI STUFF</div>; const { onEmojiClicked, show } = this.props;
return (
<div className={classNames('session-emoji-panel', show && 'show')}>
<Picker
backgroundImageFn={(_set, sheetSize) =>
`./images/emoji/emoji-sheet-${sheetSize}.png`
}
darkMode={true}
color={'#00F782'}
showPreview={true}
title={''}
onSelect={onEmojiClicked}
autoFocus={true}
// set="apple"
/>
</div>
);
} }
} }

@ -447,7 +447,7 @@ export class SessionRecording extends React.Component<Props, State> {
} }
// Start recording the stream // Start recording the stream
const media = new window.MediaRecorder(stream); const media = new window.MediaRecorder(stream, {mimeType: 'audio/webm'});
media.ondataavailable = (mediaBlob: any) => { media.ondataavailable = (mediaBlob: any) => {
this.setState({mediaBlob}, () => { this.setState({mediaBlob}, () => {
// Generate PCM waveform for playback // Generate PCM waveform for playback

@ -3,6 +3,7 @@ export type MIMEType = string & { _mimeTypeBrand: any };
export const APPLICATION_OCTET_STREAM = 'application/octet-stream' as MIMEType; export const APPLICATION_OCTET_STREAM = 'application/octet-stream' as MIMEType;
export const APPLICATION_JSON = 'application/json' as MIMEType; export const APPLICATION_JSON = 'application/json' as MIMEType;
export const AUDIO_AAC = 'audio/aac' as MIMEType; export const AUDIO_AAC = 'audio/aac' as MIMEType;
export const AUDIO_WEBM = 'audio/webm' as MIMEType;
export const AUDIO_MP3 = 'audio/mp3' as MIMEType; export const AUDIO_MP3 = 'audio/mp3' as MIMEType;
export const IMAGE_GIF = 'image/gif' as MIMEType; export const IMAGE_GIF = 'image/gif' as MIMEType;
export const IMAGE_JPEG = 'image/jpeg' as MIMEType; export const IMAGE_JPEG = 'image/jpeg' as MIMEType;

Loading…
Cancel
Save