pull/715/head
Audric Ackermann 5 years ago
parent ab0ff9c3ad
commit 213db6c1ed

@ -17,11 +17,7 @@
'use strict'; 'use strict';
window.Whisper = window.Whisper || {}; window.Whisper = window.Whisper || {};
const { Message } = window.Signal.Types; const { getAbsoluteAttachmentPath } = window.Signal.Migrations;
const {
upgradeMessageSchema,
getAbsoluteAttachmentPath,
} = window.Signal.Migrations;
const MAX_MESSAGE_BODY_LENGTH = 64 * 1024; const MAX_MESSAGE_BODY_LENGTH = 64 * 1024;
@ -337,7 +333,7 @@
}); });
this.showGroupSettings = () => { this.showGroupSettings = () => {
if(!this.groupSettings) { if (!this.groupSettings) {
this.groupSettings = new Whisper.ReactWrapperView({ this.groupSettings = new Whisper.ReactWrapperView({
className: 'group-settings', className: 'group-settings',
Component: window.Signal.Components.SessionChannelSettings, Component: window.Signal.Components.SessionChannelSettings,
@ -346,11 +342,11 @@
this.$('.conversation-content-right').append(this.groupSettings.el); this.$('.conversation-content-right').append(this.groupSettings.el);
} }
this.$('.conversation-content-right').show(); this.$('.conversation-content-right').show();
} };
this.hideGroupSettings = () => { this.hideGroupSettings = () => {
this.$('.conversation-content-right').hide(); this.$('.conversation-content-right').hide();
} };
this.memberView.render(); this.memberView.render();
@ -963,7 +959,6 @@
el[0].scrollIntoView(); el[0].scrollIntoView();
}, },
scrollToBottom() { scrollToBottom() {
// If we're above the last seen indicator, we should scroll there instead // If we're above the last seen indicator, we should scroll there instead
// Note: if we don't end up at the bottom of the conversation, button won't go away! // Note: if we don't end up at the bottom of the conversation, button won't go away!

@ -233,7 +233,7 @@ function createWindow() {
preload: path.join(__dirname, 'preload.js'), preload: path.join(__dirname, 'preload.js'),
nativeWindowOpen: true, nativeWindowOpen: true,
}, },
icon: path.join(__dirname, 'images', 'session','icon_64.png'), icon: path.join(__dirname, 'images', 'session', 'icon_64.png'),
}, },
_.pick(windowConfig, [ _.pick(windowConfig, [
'maximized', 'maximized',

@ -1,135 +1,132 @@
.group-settings { .group-settings {
display: flex;
flex-direction: column;
height: 100vh;
width: 277px;
flex-shrink: 0;
border: 1px solid #2f2f2f;
background-color: $session-shade-4;
align-items: center;
&-header {
margin-top: $session-margin-lg;
width: -webkit-fill-available;
display: flex; display: flex;
flex-direction: column; flex-direction: row;
height: 100vh;
width: 277px;
flex-shrink: 0; flex-shrink: 0;
border: 1px solid #2f2f2f;
background-color: $session-shade-4;
align-items: center;
&-header {
margin-top: $session-margin-lg;
width: -webkit-fill-available;
display: flex;
flex-direction: row;
flex-shrink: 0;
.module-avatar {
margin: auto;
}
.session-icon-button { .module-avatar {
margin: 0 $session-margin-md; margin: auto;
}
} }
h2 { .session-icon-button {
word-break: break-word; margin: 0 $session-margin-md;
} }
}
.description {
margin: $session-margin-md 0; h2 {
border: 1px solid $session-shade-6; word-break: break-word;
background-color: $session-shade-1; }
min-height: 4rem;
width: inherit; .description {
color: $session-color-white; margin: $session-margin-md 0;
text-align: center; border: 1px solid $session-shade-6;
background-color: $session-shade-1;
min-height: 4rem;
width: inherit;
color: $session-color-white;
text-align: center;
}
&-item {
display: flex;
align-items: center;
background-color: $session-shade-1;
min-height: 3rem;
font-size: 0.8rem;
color: $session-color-white;
width: -webkit-fill-available;
padding: 0 $session-margin-md;
border-bottom: 1px solid $session-shade-6;
border-top: 1px solid $session-shade-6;
transition: $session-transition-duration;
cursor: pointer;
&:hover {
@include session-dark-background-hover;
} }
}
&-item { // no double border (top and bottom) between two elements
display: flex; &-item + &-item {
align-items: center; border-top: none;
background-color: $session-shade-1; }
min-height: 3rem;
font-size: 0.8rem; // bottom button
color: $session-color-white; .session-button.square-outline.danger {
width: -webkit-fill-available; margin-top: auto;
padding: 0 $session-margin-md; width: 100%;
border-bottom: 1px solid $session-shade-6; border: none;
border-top: 1px solid $session-shade-6; height: 3.5rem;
transition: $session-transition-duration; background-color: black;
cursor: pointer; flex-shrink: 0;
}
&:hover {
@include session-dark-background-hover; .module-empty-state {
text-align: center;
}
.module-attachment-section__items {
justify-content: space-evenly;
}
.module-media {
&-gallery {
&__tab-container {
padding-top: 1rem;
}
&__tab {
color: white;
font-weight: bold;
font-size: 0.9rem;
padding: 0.6rem;
opacity: 0.8;
&--active {
border-bottom: none;
opacity: 1;
&:after {
content: ''; /* This is necessary for the pseudo element to work. */
display: block;
margin: 0 auto;
width: 70%;
padding-top: 8px;
border-bottom: 4px solid $session-color-green;
}
} }
}
} &__content {
padding: $session-margin-xs;
// no double border (top and bottom) between two elements .module-media-grid-item__image,
&-item + &-item { .module-media-grid-item {
border-top: none; height: 80px;
} width: 80px;
margin-right: 0;
// bottom button
.session-button.square-outline.danger {
margin-top: auto;
width: 100%;
border: none;
height: 3.5rem;
background-color: black;
flex-shrink: 0;
}
.module-empty-state {
text-align: center;
}
.module-attachment-section__items {
justify-content: space-evenly;
}
.module-media {
&-gallery {
&__tab-container {
padding-top: 1rem;
}
&__tab {
color: white;
font-weight: bold;
font-size: 0.9rem;
padding: 0.6rem;
opacity: 0.8;
&--active {
border-bottom: none;
opacity: 1;
&:after {
content: ""; /* This is necessary for the pseudo element to work. */
display: block;
margin: 0 auto;
width: 70%;
padding-top: 8px;
border-bottom: 4px solid $session-color-green;
}
}
}
&__content {
padding: $session-margin-xs;
.module-media-grid-item__image,
.module-media-grid-item {
height: 80px;
width: 80px;
margin-right: 0;
}
}
} }
}
} }
}
} }
.conversation-content { .conversation-content {
display: flex; display: flex;
height: inherit; height: inherit;
&-left { &-left {
flex-grow: 1; flex-grow: 1;
} }
} }

@ -16,7 +16,6 @@ interface Props {
export class AttachmentSection extends React.Component<Props> { export class AttachmentSection extends React.Component<Props> {
public render() { public render() {
return ( return (
<div className="module-attachment-section"> <div className="module-attachment-section">
<div className="module-attachment-section__items"> <div className="module-attachment-section__items">

@ -18,7 +18,6 @@ interface State {
selectedTab: 'media' | 'documents'; selectedTab: 'media' | 'documents';
} }
interface TabSelectEvent { interface TabSelectEvent {
type: 'media' | 'documents'; type: 'media' | 'documents';
} }
@ -115,13 +114,13 @@ export class MediaGallery extends React.Component<Props, State> {
return ( return (
<div className="module-media-gallery__sections"> <div className="module-media-gallery__sections">
<AttachmentSection <AttachmentSection
key="mediaItems" key="mediaItems"
i18n={window.i18n} i18n={window.i18n}
type={type} type={type}
mediaItems={mediaItems} mediaItems={mediaItems}
onItemClick={onItemClick} onItemClick={onItemClick}
/> />
</div> </div>
); );
} }

@ -40,7 +40,7 @@ const Section = ({
}) => { }) => {
const handleClick = onSelect const handleClick = onSelect
? () => { ? () => {
if(type !== SectionType.Profile) { if (type !== SectionType.Profile) {
onSelect(type); onSelect(type);
} }
} }
@ -58,7 +58,7 @@ const Section = ({
onAvatarClick={handleClick} onAvatarClick={handleClick}
/> />
); );
} }
let iconType: SessionIconType; let iconType: SessionIconType;
switch (type) { switch (type) {

@ -82,7 +82,7 @@ export class LeftPaneContactSection extends React.Component<Props, State> {
public renderHeader(): JSX.Element | undefined { public renderHeader(): JSX.Element | undefined {
const { receivedFriendRequestCount } = this.props; const { receivedFriendRequestCount } = this.props;
// The feature "organize your friends as custom list" is not included in the first release // The feature "organize your friends as custom list" is not included in the first release
const labels = [window.i18n('contactsHeader')/*, window.i18n('lists')*/]; const labels = [window.i18n('contactsHeader') /*, window.i18n('lists')*/];
return LeftPane.RENDER_HEADER( return LeftPane.RENDER_HEADER(
labels, labels,

@ -1,145 +1,159 @@
import React from 'react'; import React from 'react';
import { SessionIconButton, SessionIconSize, SessionIconType } from './icon'; import { SessionIconButton, SessionIconSize, SessionIconType } from './icon';
import { Avatar } from '../Avatar'; import { Avatar } from '../Avatar';
import { SessionButton, SessionButtonColor, SessionButtonType } from './SessionButton'; import {
SessionButton,
SessionButtonColor,
SessionButtonType,
} from './SessionButton';
import { MediaGallery } from '../conversation/media-gallery/MediaGallery'; import { MediaGallery } from '../conversation/media-gallery/MediaGallery';
import _ from 'lodash'; import _ from 'lodash';
interface Props { interface Props {
id: string; id: string;
name: string; name: string;
memberCount: number; memberCount: number;
description: string; description: string;
avatarPath: string; avatarPath: string;
onGoBack: () => void; onGoBack: () => void;
onInviteFriends: () => void; onInviteFriends: () => void;
onLeaveGroup: () => void; onLeaveGroup: () => void;
} }
export class SessionChannelSettings extends React.Component<Props, any> { export class SessionChannelSettings extends React.Component<Props, any> {
public constructor(props: Props) {
public constructor(props: Props) { super(props);
super(props);
this.state = {
this.state = { documents: Array<any>(),
documents: Array<any>(), media: Array<any>(),
media: Array<any>(), onItemClick: undefined,
onItemClick: undefined, };
}; }
}
public componentWillMount() {
public componentWillMount() { this.getMediaGalleryProps()
this.getMediaGalleryProps().then(({ documents, media, onItemClick }) => { .then(({ documents, media, onItemClick }) => {
this.setState({ this.setState({
documents, documents,
media, media,
onItemClick, onItemClick,
}); });
}).ignore(); })
} .ignore();
}
public async getMediaGalleryProps() {
// We fetch more documents than media as they dont require to be loaded public async getMediaGalleryProps() {
// into memory right away. Revisit this once we have infinite scrolling: // We fetch more documents than media as they dont require to be loaded
const DEFAULT_MEDIA_FETCH_COUNT = 50; // into memory right away. Revisit this once we have infinite scrolling:
const DEFAULT_DOCUMENTS_FETCH_COUNT = 150; const DEFAULT_MEDIA_FETCH_COUNT = 50;
const conversationId = this.props.id; const DEFAULT_DOCUMENTS_FETCH_COUNT = 150;
const rawMedia = await window.Signal.Data.getMessagesWithVisualMediaAttachments( const conversationId = this.props.id;
conversationId, const rawMedia = await window.Signal.Data.getMessagesWithVisualMediaAttachments(
{ conversationId,
limit: DEFAULT_MEDIA_FETCH_COUNT, {
MessageCollection: window.Whisper.MessageCollection, limit: DEFAULT_MEDIA_FETCH_COUNT,
} MessageCollection: window.Whisper.MessageCollection,
); }
const rawDocuments = await window.Signal.Data.getMessagesWithFileAttachments( );
conversationId, const rawDocuments = await window.Signal.Data.getMessagesWithFileAttachments(
{ conversationId,
limit: DEFAULT_DOCUMENTS_FETCH_COUNT, {
MessageCollection: window.Whisper.MessageCollection, limit: DEFAULT_DOCUMENTS_FETCH_COUNT,
} MessageCollection: window.Whisper.MessageCollection,
}
);
// First we upgrade these messages to ensure that they have thumbnails
const max = rawMedia.length;
for (let i = 0; i < max; i += 1) {
const message = rawMedia[i];
const { schemaVersion } = message;
if (schemaVersion < message.VERSION_NEEDED_FOR_DISPLAY) {
// Yep, we really do want to wait for each of these
// eslint-disable-next-line no-await-in-loop
rawMedia[i] = await window.Signal.Migrations.upgradeMessageSchema(
message
); );
// eslint-disable-next-line no-await-in-loop
await window.Signal.Data.saveMessage(rawMedia[i], {
Message: window.Whisper.Message,
});
}
}
// First we upgrade these messages to ensure that they have thumbnails // tslint:disable-next-line: underscore-consistent-invocation
const max = rawMedia.length; const media = _.flatten(
for (let i = 0; i < max; i += 1) { rawMedia.map((message: { attachments: any }) => {
const message = rawMedia[i]; const { attachments } = message;
const { schemaVersion } = message;
return (attachments || [])
if (schemaVersion < message.VERSION_NEEDED_FOR_DISPLAY) { .filter(
// Yep, we really do want to wait for each of these (attachment: { thumbnail: any; pending: any; error: any }) =>
// eslint-disable-next-line no-await-in-loop attachment.thumbnail && !attachment.pending && !attachment.error
rawMedia[i] = await window.Signal.Migrations.upgradeMessageSchema(message); )
// eslint-disable-next-line no-await-in-loop .map(
await window.Signal.Data.saveMessage(rawMedia[i], { (
Message: window.Whisper.Message, attachment: { path?: any; contentType?: any; thumbnail?: any },
}); index: any
) => {
const { thumbnail } = attachment;
return {
objectURL: window.Signal.Migrations.getAbsoluteAttachmentPath(
attachment.path
),
thumbnailObjectUrl: thumbnail
? window.Signal.Migrations.getAbsoluteAttachmentPath(
thumbnail.path
)
: null,
contentType: attachment.contentType,
index,
attachment,
message,
};
} }
} );
})
);
// tslint:disable-next-line: underscore-consistent-invocation // Unlike visual media, only one non-image attachment is supported
const media = _.flatten( const documents = rawDocuments.map(
rawMedia.map((message: { attachments: any }) => { (message: { attachments: Array<any> }) => {
const { attachments } = message; const attachments = message.attachments || [];
const attachment = attachments[0];
return (attachments || [])
.filter(
(attachment: { thumbnail: any; pending: any; error: any }) =>
attachment.thumbnail &&
!attachment.pending &&
!attachment.error
)
.map((attachment: { path?: any; contentType?: any; thumbnail?: any }, index: any) => {
const { thumbnail } = attachment;
return {
objectURL: window.Signal.Migrations.getAbsoluteAttachmentPath(attachment.path),
thumbnailObjectUrl: thumbnail
? window.Signal.Migrations.getAbsoluteAttachmentPath(thumbnail.path)
: null,
contentType: attachment.contentType,
index,
attachment,
message,
};
});
})
);
// Unlike visual media, only one non-image attachment is supported
const documents = rawDocuments.map((message: { attachments: Array<any> }) => {
const attachments = message.attachments || [];
const attachment = attachments[0];
return {
contentType: attachment.contentType,
index: 0,
attachment,
message,
};
});
const saveAttachment = async ({ attachment, message }: any = {}) => { return {
const timestamp = message.received_at; contentType: attachment.contentType,
window.Signal.Types.Attachment.save({ index: 0,
attachment, attachment,
document, message,
getAbsolutePath: window.Signal.Migrations.getAbsoluteAttachmentPath,
timestamp,
});
}; };
}
);
const saveAttachment = async ({ attachment, message }: any = {}) => {
const timestamp = message.received_at;
window.Signal.Types.Attachment.save({
attachment,
document,
getAbsolutePath: window.Signal.Migrations.getAbsoluteAttachmentPath,
timestamp,
});
};
const onItemClick = async ({ message, attachment, type }: any) => {
switch (type) {
case 'documents':
case 'media': {
saveAttachment({ message, attachment }).ignore();
break;
}
const onItemClick = async ({ message, attachment, type } : any) => { /*case 'media': {
switch (type) {
case 'documents':
case 'media': {
saveAttachment({ message, attachment }).ignore();
break;
}
/*case 'media': {
const selectedIndex = media.findIndex( const selectedIndex = media.findIndex(
mediaMessage => mediaMessage.attachment.path === attachment.path mediaMessage => mediaMessage.attachment.path === attachment.path
); );
@ -157,47 +171,80 @@ export class SessionChannelSettings extends React.Component<Props, any> {
break; break;
}*/ }*/
default: default:
throw new TypeError(`Unknown attachment type: '${type}'`); throw new TypeError(`Unknown attachment type: '${type}'`);
} }
}; };
return { return {
documents, documents,
media, media,
onItemClick, onItemClick,
}; };
} }
public render() {
public render() { const { memberCount, name, onLeaveGroup } = this.props;
const { memberCount, name, onLeaveGroup } = this.props; const { documents, media, onItemClick } = this.state;
const { documents, media, onItemClick } = this.state;
return (
return ( <div className="group-settings">
<div className="group-settings"> {this.renderHeader()}
{this.renderHeader()} <h2>{name}</h2>
<h2>{name}</h2> {memberCount && (
{memberCount && <div className="text-subtle">{window.i18n('members', memberCount)}</div>} <div className="text-subtle">
<input className="description" placeholder={window.i18n('description')} /> {window.i18n('members', memberCount)}
</div>
<div className="group-settings-item" >{window.i18n('notifications')}</div> )}
<div className="group-settings-item" >{window.i18n('disappearingMessages')}</div> <input
<MediaGallery documents={documents} media={media} onItemClick={onItemClick} /> className="description"
<SessionButton text={window.i18n('leaveGroup')} buttonColor={SessionButtonColor.Danger} buttonType={SessionButtonType.SquareOutline} onClick={onLeaveGroup}/> placeholder={window.i18n('description')}
</div>); />
}
<div className="group-settings-item">
{window.i18n('notifications')}
private renderHeader() { </div>
const { id, onGoBack, onInviteFriends, avatarPath } = this.props; <div className="group-settings-item">
{window.i18n('disappearingMessages')}
return ( </div>
<div className="group-settings-header"> <MediaGallery
<SessionIconButton iconType={SessionIconType.Chevron} iconSize={SessionIconSize.Medium} iconRotation={90} onClick={onGoBack} /> documents={documents}
<Avatar avatarPath={avatarPath} phoneNumber={id} conversationType="group" size={80} /> media={media}
<SessionIconButton iconType={SessionIconType.AddUser} iconSize={SessionIconSize.Medium} onClick={onInviteFriends} /> onItemClick={onItemClick}
</div> />
); <SessionButton
} text={window.i18n('leaveGroup')}
buttonColor={SessionButtonColor.Danger}
buttonType={SessionButtonType.SquareOutline}
onClick={onLeaveGroup}
/>
</div>
);
}
private renderHeader() {
const { id, onGoBack, onInviteFriends, avatarPath } = this.props;
return (
<div className="group-settings-header">
<SessionIconButton
iconType={SessionIconType.Chevron}
iconSize={SessionIconSize.Medium}
iconRotation={90}
onClick={onGoBack}
/>
<Avatar
avatarPath={avatarPath}
phoneNumber={id}
conversationType="group"
size={80}
/>
<SessionIconButton
iconType={SessionIconType.AddUser}
iconSize={SessionIconSize.Medium}
onClick={onInviteFriends}
/>
</div>
);
}
} }

Loading…
Cancel
Save