initial commit

This commit is contained in:
unknown 2021-07-28 18:45:52 +05:30
commit 026f835a87
176 changed files with 10613 additions and 0 deletions

View file

@ -0,0 +1,288 @@
import EventEmitter from 'events';
import appDispatcher from '../dispatcher';
import cons from './cons';
class RoomList extends EventEmitter {
constructor(matrixClient) {
super();
this.matrixClient = matrixClient;
this.mDirects = this.getMDirects();
this.inviteDirects = new Set();
this.inviteSpaces = new Set();
this.inviteRooms = new Set();
this.directs = new Set();
this.spaces = new Set();
this.rooms = new Set();
this.processingRooms = new Map();
this._populateRooms();
this._listenEvents();
appDispatcher.register(this.roomActions.bind(this));
}
roomActions(action) {
const addRoom = (roomId, isDM) => {
const myRoom = this.matrixClient.getRoom(roomId);
if (myRoom === null) return false;
if (isDM) this.directs.add(roomId);
else if (myRoom.isSpaceRoom()) this.spaces.add(roomId);
else this.rooms.add(roomId);
return true;
};
const actions = {
[cons.actions.room.JOIN]: () => {
if (addRoom(action.roomId, action.isDM)) {
setTimeout(() => {
this.emit(cons.events.roomList.ROOM_JOINED, action.roomId);
this.emit(cons.events.roomList.ROOMLIST_UPDATED);
}, 100);
} else {
this.processingRooms.set(action.roomId, {
roomId: action.roomId,
isDM: action.isDM,
task: 'JOIN',
});
}
},
[cons.actions.room.CREATE]: () => {
if (addRoom(action.roomId, action.isDM)) {
setTimeout(() => {
this.emit(cons.events.roomList.ROOM_CREATED, action.roomId);
this.emit(cons.events.roomList.ROOM_JOINED, action.roomId);
this.emit(cons.events.roomList.ROOMLIST_UPDATED);
}, 100);
} else {
this.processingRooms.set(action.roomId, {
roomId: action.roomId,
isDM: action.isDM,
task: 'CREATE',
});
}
},
};
actions[action.type]?.();
}
getMDirects() {
const mDirectsId = new Set();
const mDirect = this.matrixClient
.getAccountData('m.direct')
?.getContent();
if (typeof mDirect === 'undefined') return mDirectsId;
Object.keys(mDirect).forEach((direct) => {
mDirect[direct].forEach((directId) => mDirectsId.add(directId));
});
return mDirectsId;
}
_populateRooms() {
this.directs.clear();
this.spaces.clear();
this.rooms.clear();
this.inviteDirects.clear();
this.inviteSpaces.clear();
this.inviteRooms.clear();
this.matrixClient.getRooms().forEach((room) => {
const { roomId } = room;
const tombstone = room.currentState.events.get('m.room.tombstone');
if (typeof tombstone !== 'undefined') {
const repRoomId = tombstone.get('').getContent().replacement_room;
const repRoomMembership = this.matrixClient.getRoom(repRoomId)?.getMyMembership();
if (repRoomMembership === 'join') return;
}
if (room.getMyMembership() === 'invite') {
if (this._isDMInvite(room)) this.inviteDirects.add(roomId);
else if (room.isSpaceRoom()) this.inviteSpaces.add(roomId);
else this.inviteRooms.add(roomId);
return;
}
if (room.getMyMembership() !== 'join') return;
if (this.mDirects.has(roomId)) this.directs.add(roomId);
else if (room.isSpaceRoom()) this.spaces.add(roomId);
else this.rooms.add(roomId);
});
}
_isDMInvite(room) {
const me = room.getMember(this.matrixClient.getUserId());
const myEventContent = me.events.member.getContent();
return myEventContent.membership === 'invite' && myEventContent.is_direct;
}
_listenEvents() {
// Update roomList when m.direct changes
this.matrixClient.on('accountData', (event) => {
if (event.getType() !== 'm.direct') return;
const latestMDirects = this.getMDirects();
latestMDirects.forEach((directId) => {
const myRoom = this.matrixClient.getRoom(directId);
if (this.mDirects.has(directId)) return;
// Update mDirects
this.mDirects.add(directId);
if (myRoom === null) return;
if (this._isDMInvite(myRoom)) return;
if (myRoom.getMyMembership === 'join' && !this.directs.has(directId)) {
this.directs.add(directId);
}
// Newly added room.
// at this time my membership can be invite | join
if (myRoom.getMyMembership() === 'join' && this.rooms.has(directId)) {
// found a DM which accidentally gets added to this.rooms
this.rooms.delete(directId);
this.emit(cons.events.roomList.ROOMLIST_UPDATED);
}
});
});
this.matrixClient.on('Room.name', () => {
this.emit(cons.events.roomList.ROOMLIST_UPDATED);
});
this.matrixClient.on('Room.receipt', (event) => {
if (event.getType() === 'm.receipt') {
const evContent = event.getContent();
const userId = Object.keys(evContent[Object.keys(evContent)[0]]['m.read'])[0];
if (userId !== this.matrixClient.getUserId()) return;
this.emit(cons.events.roomList.ROOMLIST_UPDATED);
}
});
this.matrixClient.on('RoomState.events', (event) => {
if (event.getType() !== 'm.room.join_rules') return;
this.emit(cons.events.roomList.ROOMLIST_UPDATED);
});
this.matrixClient.on('Room.myMembership', (room, membership, prevMembership) => {
// room => prevMembership = null | invite | join | leave | kick | ban | unban
// room => membership = invite | join | leave | kick | ban | unban
const { roomId } = room;
if (membership === 'unban') return;
// When user_reject/sender_undo room invite
if (prevMembership === 'invite') {
if (this.inviteDirects.has(roomId)) this.inviteDirects.delete(roomId);
else if (this.inviteSpaces.has(roomId)) this.inviteSpaces.delete(roomId);
else this.inviteRooms.delete(roomId);
this.emit(cons.events.roomList.INVITELIST_UPDATED, roomId);
}
// When user get invited
if (membership === 'invite') {
if (this._isDMInvite(room)) this.inviteDirects.add(roomId);
else if (room.isSpaceRoom()) this.inviteSpaces.add(roomId);
else this.inviteRooms.add(roomId);
this.emit(cons.events.roomList.INVITELIST_UPDATED, roomId);
return;
}
// When user join room (first time) or start DM.
if ((prevMembership === null || prevMembership === 'invite') && membership === 'join') {
// when user create room/DM OR accept room/dm invite from this client.
// we will update this.rooms/this.directs with user action
if (this.directs.has(roomId) || this.spaces.has(roomId) || this.rooms.has(roomId)) return;
if (this.processingRooms.has(roomId)) {
const procRoomInfo = this.processingRooms.get(roomId);
if (procRoomInfo.isDM) this.directs.add(roomId);
else if (room.isSpaceRoom()) this.spaces.add(roomId);
else this.rooms.add(roomId);
if (procRoomInfo.task === 'CREATE') this.emit(cons.events.roomList.ROOM_CREATED, roomId);
this.emit(cons.events.roomList.ROOM_JOINED, roomId);
this.emit(cons.events.roomList.ROOMLIST_UPDATED);
this.processingRooms.delete(roomId);
return;
}
if (room.isSpaceRoom()) {
this.spaces.add(roomId);
this.emit(cons.events.roomList.ROOM_JOINED, roomId);
this.emit(cons.events.roomList.ROOMLIST_UPDATED);
return;
}
// below code intented to work when user create room/DM
// OR accept room/dm invite from other client.
// and we have to update our client. (it's ok to have 10sec delay)
// create a buffer of 10sec and HOPE client.accoundData get updated
// then accoundData event listener will update this.mDirects.
// and we will be able to know if it's a DM.
// ----------
// less likely situation:
// if we don't get accountData with 10sec then:
// we will temporary add it to this.rooms.
// and in future when accountData get updated
// accountData listener will automatically goona REMOVE it from this.rooms
// and will ADD it to this.directs
// and emit the cons.events.roomList.ROOMLIST_UPDATED to update the UI.
setTimeout(() => {
if (this.directs.has(roomId) || this.spaces.has(roomId) || this.rooms.has(roomId)) return;
if (this.mDirects.has(roomId)) this.directs.add(roomId);
else this.rooms.add(roomId);
this.emit(cons.events.roomList.ROOM_JOINED, roomId);
this.emit(cons.events.roomList.ROOMLIST_UPDATED);
}, 10000);
return;
}
// when room is a DM add/remove it from DM's and return.
if (this.directs.has(roomId)) {
if (membership === 'leave' || membership === 'kick' || membership === 'ban') {
this.directs.delete(roomId);
this.emit(cons.events.roomList.ROOM_LEAVED, roomId);
}
}
if (this.mDirects.has(roomId)) {
if (membership === 'join') {
this.directs.add(roomId);
this.emit(cons.events.roomList.ROOM_JOINED, roomId);
}
this.emit(cons.events.roomList.ROOMLIST_UPDATED);
return;
}
// when room is not a DM add/remove it from rooms.
if (membership === 'leave' || membership === 'kick' || membership === 'ban') {
if (room.isSpaceRoom()) this.spaces.delete(roomId);
else this.rooms.delete(roomId);
this.emit(cons.events.roomList.ROOM_LEAVED, roomId);
}
if (membership === 'join') {
if (room.isSpaceRoom()) this.spaces.add(roomId);
else this.rooms.add(roomId);
this.emit(cons.events.roomList.ROOM_JOINED, roomId);
}
this.emit(cons.events.roomList.ROOMLIST_UPDATED);
});
this.matrixClient.on('Room.timeline', () => {
this.emit(cons.events.roomList.ROOMLIST_UPDATED);
});
}
}
export default RoomList;

View file

@ -0,0 +1,161 @@
import EventEmitter from 'events';
import initMatrix from '../initMatrix';
import cons from './cons';
class RoomTimeline extends EventEmitter {
constructor(roomId) {
super();
this.matrixClient = initMatrix.matrixClient;
this.roomId = roomId;
this.room = this.matrixClient.getRoom(roomId);
this.timeline = this.room.timeline;
this.editedTimeline = this.getEditedTimeline();
this.reactionTimeline = this.getReactionTimeline();
this.isOngoingPagination = false;
this.ongoingDecryptionCount = 0;
this.typingMembers = new Set();
this._listenRoomTimeline = (event, room) => {
if (room.roomId !== this.roomId) return;
if (event.isEncrypted()) {
this.ongoingDecryptionCount += 1;
return;
}
this.timeline = this.room.timeline;
if (this.isEdited(event)) {
this.addToMap(this.editedTimeline, event);
}
if (this.isReaction(event)) {
this.addToMap(this.reactionTimeline, event);
}
if (this.ongoingDecryptionCount !== 0) return;
this.emit(cons.events.roomTimeline.EVENT);
};
this._listenDecryptEvent = (event) => {
if (event.getRoomId() !== this.roomId) return;
if (this.ongoingDecryptionCount > 0) this.ongoingDecryptionCount -= 1;
this.timeline = this.room.timeline;
if (this.ongoingDecryptionCount !== 0) return;
this.emit(cons.events.roomTimeline.EVENT);
};
this._listenTypingEvent = (event, member) => {
if (member.roomId !== this.roomId) return;
const isTyping = member.typing;
if (isTyping) this.typingMembers.add(member.userId);
else this.typingMembers.delete(member.userId);
this.emit(cons.events.roomTimeline.TYPING_MEMBERS_UPDATED, new Set([...this.typingMembers]));
};
this._listenReciptEvent = (event, room) => {
if (room.roomId !== this.roomId) return;
const receiptContent = event.getContent();
if (this.timeline.length === 0) return;
const tmlLastEvent = this.timeline[this.timeline.length - 1];
const lastEventId = tmlLastEvent.getId();
const lastEventRecipt = receiptContent[lastEventId];
if (typeof lastEventRecipt === 'undefined') return;
if (lastEventRecipt['m.read']) {
this.emit(cons.events.roomTimeline.READ_RECEIPT);
}
};
this.matrixClient.on('Room.timeline', this._listenRoomTimeline);
this.matrixClient.on('Event.decrypted', this._listenDecryptEvent);
this.matrixClient.on('RoomMember.typing', this._listenTypingEvent);
this.matrixClient.on('Room.receipt', this._listenReciptEvent);
// TODO: remove below line when release
window.selectedRoom = this;
if (this.isEncryptedRoom()) this.room.decryptAllEvents();
}
isEncryptedRoom() {
return this.matrixClient.isRoomEncrypted(this.roomId);
}
// eslint-disable-next-line class-methods-use-this
isEdited(mEvent) {
return mEvent.getRelation()?.rel_type === 'm.replace';
}
// eslint-disable-next-line class-methods-use-this
getRelateToId(mEvent) {
const relation = mEvent.getRelation();
return relation && relation.event_id;
}
addToMap(myMap, mEvent) {
const relateToId = this.getRelateToId(mEvent);
if (relateToId === null) return null;
if (typeof myMap.get(relateToId) === 'undefined') myMap.set(relateToId, []);
myMap.get(relateToId).push(mEvent);
return mEvent;
}
getEditedTimeline() {
const mReplace = new Map();
this.timeline.forEach((mEvent) => {
if (this.isEdited(mEvent)) {
this.addToMap(mReplace, mEvent);
}
});
return mReplace;
}
// eslint-disable-next-line class-methods-use-this
isReaction(mEvent) {
return mEvent.getType() === 'm.reaction';
}
getReactionTimeline() {
const mReaction = new Map();
this.timeline.forEach((mEvent) => {
if (this.isReaction(mEvent)) {
this.addToMap(mReaction, mEvent);
}
});
return mReaction;
}
paginateBack() {
if (this.isOngoingPagination) return;
this.isOngoingPagination = true;
const MSG_LIMIT = 30;
this.matrixClient.scrollback(this.room, MSG_LIMIT).then(async (room) => {
if (room.oldState.paginationToken === null) {
// We have reached start of the timeline
this.isOngoingPagination = false;
if (this.isEncryptedRoom()) await this.room.decryptAllEvents();
this.emit(cons.events.roomTimeline.PAGINATED, false);
return;
}
this.editedTimeline = this.getEditedTimeline();
this.reactionTimeline = this.getReactionTimeline();
this.isOngoingPagination = false;
if (this.isEncryptedRoom()) await this.room.decryptAllEvents();
this.emit(cons.events.roomTimeline.PAGINATED, true);
});
}
removeInternalListeners() {
this.matrixClient.removeListener('Room.timeline', this._listenRoomTimeline);
this.matrixClient.removeListener('Event.decrypted', this._listenDecryptEvent);
this.matrixClient.removeListener('RoomMember.typing', this._listenTypingEvent);
this.matrixClient.removeListener('Room.receipt', this._listenReciptEvent);
}
}
export default RoomTimeline;

View file

@ -0,0 +1,276 @@
import EventEmitter from 'events';
import encrypt from 'browser-encrypt-attachment';
import cons from './cons';
function getImageDimension(file) {
return new Promise((resolve) => {
const img = new Image();
img.onload = async () => {
resolve({
w: img.width,
h: img.height,
});
};
img.src = URL.createObjectURL(file);
});
}
function loadVideo(videoFile) {
return new Promise((resolve, reject) => {
const video = document.createElement('video');
video.preload = 'metadata';
video.playsInline = true;
video.muted = true;
const reader = new FileReader();
reader.onload = (ev) => {
// Wait until we have enough data to thumbnail the first frame.
video.onloadeddata = async () => {
resolve(video);
video.pause();
};
video.onerror = (e) => {
reject(e);
};
video.src = ev.target.result;
video.load();
video.play();
};
reader.onerror = (e) => {
reject(e);
};
reader.readAsDataURL(videoFile);
});
}
function getVideoThumbnail(video, width, height, mimeType) {
return new Promise((resolve) => {
const MAX_WIDTH = 800;
const MAX_HEIGHT = 600;
let targetWidth = width;
let targetHeight = height;
if (targetHeight > MAX_HEIGHT) {
targetWidth = Math.floor(targetWidth * (MAX_HEIGHT / targetHeight));
targetHeight = MAX_HEIGHT;
}
if (targetWidth > MAX_WIDTH) {
targetHeight = Math.floor(targetHeight * (MAX_WIDTH / targetWidth));
targetWidth = MAX_WIDTH;
}
const canvas = document.createElement('canvas');
canvas.width = targetWidth;
canvas.height = targetHeight;
const context = canvas.getContext('2d');
context.drawImage(video, 0, 0, targetWidth, targetHeight);
canvas.toBlob((thumbnail) => {
resolve({
thumbnail,
info: {
w: targetWidth,
h: targetHeight,
mimetype: thumbnail.type,
size: thumbnail.size,
},
});
}, mimeType);
});
}
class RoomsInput extends EventEmitter {
constructor(mx) {
super();
this.matrixClient = mx;
this.roomIdToInput = new Map();
}
cleanEmptyEntry(roomId) {
const input = this.getInput(roomId);
const isEmpty = typeof input.attachment === 'undefined'
&& (typeof input.message === 'undefined' || input.message === '');
if (isEmpty) {
this.roomIdToInput.delete(roomId);
}
}
getInput(roomId) {
return this.roomIdToInput.get(roomId) || {};
}
setMessage(roomId, message) {
const input = this.getInput(roomId);
input.message = message;
this.roomIdToInput.set(roomId, input);
if (message === '') this.cleanEmptyEntry(roomId);
}
getMessage(roomId) {
const input = this.getInput(roomId);
if (typeof input.message === 'undefined') return '';
return input.message;
}
setAttachment(roomId, file) {
const input = this.getInput(roomId);
input.attachment = {
file,
};
this.roomIdToInput.set(roomId, input);
}
getAttachment(roomId) {
const input = this.getInput(roomId);
if (typeof input.attachment === 'undefined') return null;
return input.attachment.file;
}
cancelAttachment(roomId) {
const input = this.getInput(roomId);
if (typeof input.attachment === 'undefined') return;
const { uploadingPromise } = input.attachment;
if (uploadingPromise) {
this.matrixClient.cancelUpload(uploadingPromise);
delete input.attachment.uploadingPromise;
}
if (input.message) {
delete input.attachment;
delete input.isSending;
this.roomIdToInput.set(roomId, input);
} else {
this.roomIdToInput.delete(roomId);
}
this.emit(cons.events.roomsInput.ATTACHMENT_CANCELED, roomId);
}
isSending(roomId) {
return this.roomIdToInput.get(roomId)?.isSending || false;
}
async sendInput(roomId) {
const input = this.getInput(roomId);
input.isSending = true;
this.roomIdToInput.set(roomId, input);
if (input.attachment) {
await this.sendFile(roomId, input.attachment.file);
}
if (this.getMessage(roomId).trim() !== '') {
const content = {
body: input.message,
msgtype: 'm.text',
};
this.matrixClient.sendMessage(roomId, content);
}
if (this.isSending(roomId)) this.roomIdToInput.delete(roomId);
this.emit(cons.events.roomsInput.MESSAGE_SENT, roomId);
}
async sendFile(roomId, file) {
const fileType = file.type.slice(0, file.type.indexOf('/'));
const info = {
mimetype: file.type,
size: file.size,
};
const content = { info };
let uploadData = null;
if (fileType === 'image') {
const imgDimension = await getImageDimension(file);
info.w = imgDimension.w;
info.h = imgDimension.h;
content.msgtype = 'm.image';
content.body = file.name || 'Image';
} else if (fileType === 'video') {
content.msgtype = 'm.video';
content.body = file.name || 'Video';
try {
const video = await loadVideo(file);
info.w = video.videoWidth;
info.h = video.videoHeight;
const thumbnailData = await getVideoThumbnail(video, video.videoWidth, video.videoHeight, 'image/jpeg');
const thumbnailUploadData = await this.uploadFile(roomId, thumbnailData.thumbnail);
info.thumbnail_info = thumbnailData.info;
if (this.matrixClient.isRoomEncrypted(roomId)) {
info.thumbnail_file = thumbnailUploadData.file;
} else {
info.thumbnail_url = thumbnailUploadData.url;
}
} catch (e) {
this.emit(cons.events.roomsInput.FILE_UPLOAD_CANCELED, roomId);
return;
}
} else if (fileType === 'audio') {
content.msgtype = 'm.audio';
content.body = file.name || 'Audio';
} else {
content.msgtype = 'm.file';
content.body = file.name || 'File';
}
try {
uploadData = await this.uploadFile(roomId, file, (data) => {
// data have two properties: data.loaded, data.total
this.emit(cons.events.roomsInput.UPLOAD_PROGRESS_CHANGES, roomId, data);
});
this.emit(cons.events.roomsInput.FILE_UPLOADED, roomId);
} catch (e) {
this.emit(cons.events.roomsInput.FILE_UPLOAD_CANCELED, roomId);
return;
}
if (this.matrixClient.isRoomEncrypted(roomId)) {
content.file = uploadData.file;
await this.matrixClient.sendMessage(roomId, content);
} else {
content.url = uploadData.url;
await this.matrixClient.sendMessage(roomId, content);
}
}
async uploadFile(roomId, file, progressHandler) {
const isEncryptedRoom = this.matrixClient.isRoomEncrypted(roomId);
let encryptInfo = null;
let encryptBlob = null;
if (isEncryptedRoom) {
const dataBuffer = await file.arrayBuffer();
if (typeof this.getInput(roomId).attachment === 'undefined') throw new Error('Attachment canceled');
const encryptedResult = await encrypt.encryptAttachment(dataBuffer);
if (typeof this.getInput(roomId).attachment === 'undefined') throw new Error('Attachment canceled');
encryptInfo = encryptedResult.info;
encryptBlob = new Blob([encryptedResult.data]);
}
const uploadingPromise = this.matrixClient.uploadContent(isEncryptedRoom ? encryptBlob : file, {
// don't send filename if room is encrypted.
includeFilename: !isEncryptedRoom,
progressHandler,
});
const input = this.getInput(roomId);
input.attachment.uploadingPromise = uploadingPromise;
this.roomIdToInput.set(roomId, input);
const url = await uploadingPromise;
delete input.attachment.uploadingPromise;
this.roomIdToInput.set(roomId, input);
if (isEncryptedRoom) {
encryptInfo.url = url;
if (file.type) encryptInfo.mimetype = file.type;
return { file: encryptInfo };
}
return { url };
}
}
export default RoomsInput;

19
src/client/state/auth.js Normal file
View file

@ -0,0 +1,19 @@
import cons from './cons';
function getSecret(key) {
return localStorage.getItem(key);
}
const isAuthanticated = () => getSecret(cons.secretKey.ACCESS_TOKEN) !== null;
const secret = {
accessToken: getSecret(cons.secretKey.ACCESS_TOKEN),
deviceId: getSecret(cons.secretKey.DEVICE_ID),
userId: getSecret(cons.secretKey.USER_ID),
baseUrl: getSecret(cons.secretKey.BASE_URL),
};
export {
isAuthanticated,
secret,
};

65
src/client/state/cons.js Normal file
View file

@ -0,0 +1,65 @@
const cons = {
secretKey: {
ACCESS_TOKEN: 'cinny_access_token',
DEVICE_ID: 'cinny_device_id',
USER_ID: 'cinny_user_id',
BASE_URL: 'cinny_hs_base_url',
},
DEVICE_DISPLAY_NAME: 'Cinny Web',
actions: {
navigation: {
CHANGE_TAB: 'CHANGE_TAB',
SELECT_ROOM: 'SELECT_ROOM',
TOGGLE_PEOPLE_DRAWER: 'TOGGLE_PEOPLE_DRAWER',
OPEN_INVITE_LIST: 'OPEN_INVITE_LIST',
OPEN_PUBLIC_CHANNELS: 'OPEN_PUBLIC_CHANNELS',
OPEN_CREATE_CHANNEL: 'OPEN_CREATE_CHANNEL',
OPEN_INVITE_USER: 'OPEN_INVITE_USER',
OPEN_SETTINGS: 'OPEN_SETTINGS',
},
room: {
JOIN: 'JOIN',
LEAVE: 'LEAVE',
CREATE: 'CREATE',
error: {
CREATE: 'CREATE',
},
},
},
events: {
navigation: {
TAB_CHANGED: 'TAB_CHANGED',
ROOM_SELECTED: 'ROOM_SELECTED',
PEOPLE_DRAWER_TOGGLED: 'PEOPLE_DRAWER_TOGGLED',
INVITE_LIST_OPENED: 'INVITE_LIST_OPENED',
PUBLIC_CHANNELS_OPENED: 'PUBLIC_CHANNELS_OPENED',
CREATE_CHANNEL_OPENED: 'CREATE_CHANNEL_OPENED',
INVITE_USER_OPENED: 'INVITE_USER_OPENED',
SETTINGS_OPENED: 'SETTINGS_OPENED',
},
roomList: {
ROOMLIST_UPDATED: 'ROOMLIST_UPDATED',
INVITELIST_UPDATED: 'INVITELIST_UPDATED',
ROOM_JOINED: 'ROOM_JOINED',
ROOM_LEAVED: 'ROOM_LEAVED',
ROOM_CREATED: 'ROOM_CREATED',
},
roomTimeline: {
EVENT: 'EVENT',
PAGINATED: 'PAGINATED',
TYPING_MEMBERS_UPDATED: 'TYPING_MEMBERS_UPDATED',
READ_RECEIPT: 'READ_RECEIPT',
},
roomsInput: {
MESSAGE_SENT: 'MESSAGE_SENT',
FILE_UPLOADED: 'FILE_UPLOADED',
UPLOAD_PROGRESS_CHANGES: 'UPLOAD_PROGRESS_CHANGES',
FILE_UPLOAD_CANCELED: 'FILE_UPLOAD_CANCELED',
ATTACHMENT_CANCELED: 'ATTACHMENT_CANCELED',
},
},
};
Object.freeze(cons);
export default cons;

View file

@ -0,0 +1,59 @@
import EventEmitter from 'events';
import appDispatcher from '../dispatcher';
import cons from './cons';
class Navigation extends EventEmitter {
constructor() {
super();
this.activeTab = 'channels';
this.selectedRoom = null;
this.isPeopleDrawerVisible = true;
}
getActiveTab() {
return this.activeTab;
}
getActiveRoom() {
return this.selectedRoom;
}
navigate(action) {
const actions = {
[cons.actions.navigation.CHANGE_TAB]: () => {
this.activeTab = action.tabId;
this.emit(cons.events.navigation.TAB_CHANGED, this.activeTab);
},
[cons.actions.navigation.SELECT_ROOM]: () => {
this.selectedRoom = action.roomId;
this.emit(cons.events.navigation.ROOM_SELECTED, this.selectedRoom);
},
[cons.actions.navigation.TOGGLE_PEOPLE_DRAWER]: () => {
this.isPeopleDrawerVisible = !this.isPeopleDrawerVisible;
this.emit(cons.events.navigation.PEOPLE_DRAWER_TOGGLED, this.isPeopleDrawerVisible);
},
[cons.actions.navigation.OPEN_INVITE_LIST]: () => {
this.emit(cons.events.navigation.INVITE_LIST_OPENED);
},
[cons.actions.navigation.OPEN_PUBLIC_CHANNELS]: () => {
this.emit(cons.events.navigation.PUBLIC_CHANNELS_OPENED);
},
[cons.actions.navigation.OPEN_CREATE_CHANNEL]: () => {
this.emit(cons.events.navigation.CREATE_CHANNEL_OPENED);
},
[cons.actions.navigation.OPEN_INVITE_USER]: () => {
this.emit(cons.events.navigation.INVITE_USER_OPENED, action.roomId);
},
[cons.actions.navigation.OPEN_SETTINGS]: () => {
this.emit(cons.events.navigation.SETTINGS_OPENED);
},
};
actions[action.type]?.();
}
}
const navigation = new Navigation();
appDispatcher.register(navigation.navigate.bind(navigation));
export default navigation;

View file

@ -0,0 +1,36 @@
class Settings {
constructor() {
this.themes = ['', 'silver-theme', 'dark-theme', 'butter-theme'];
this.themeIndex = this.getThemeIndex();
}
getThemeIndex() {
if (typeof this.themeIndex === 'number') return this.themeIndex;
let settings = localStorage.getItem('settings');
if (settings === null) return 0;
settings = JSON.parse(settings);
if (typeof settings.themeIndex === 'undefined') return 0;
// eslint-disable-next-line radix
return parseInt(settings.themeIndex);
}
getThemeName() {
return this.themes[this.themeIndex];
}
setTheme(themeIndex) {
const appBody = document.getElementById('appBody');
this.themes.forEach((themeName) => {
if (themeName === '') return;
appBody.classList.remove(themeName);
});
if (this.themes[themeIndex] !== '') appBody.classList.add(this.themes[themeIndex]);
localStorage.setItem('settings', JSON.stringify({ themeIndex }));
this.themeIndex = themeIndex;
}
}
const settings = new Settings();
export default settings;