mirror of
https://github.com/darkwire/darkwire.io.git
synced 2025-08-01 22:44:10 +00:00
Compare commits
4 Commits
6d0b703102
...
08071fba44
Author | SHA1 | Date | |
---|---|---|---|
|
08071fba44 | ||
|
a6ec211587 | ||
|
bceedaa2e8 | ||
|
b5e654f833 |
8
.dockerignore
Normal file
8
.dockerignore
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
*
|
||||||
|
!package.json
|
||||||
|
!yarn.lock
|
||||||
|
!docker-entrypoint.sh
|
||||||
|
!build.sh
|
||||||
|
!server/*
|
||||||
|
!client/*
|
||||||
|
**/node_modules/*
|
@ -17,7 +17,6 @@
|
|||||||
"classnames": "^2.3.2",
|
"classnames": "^2.3.2",
|
||||||
"clipboard": "^2.0.11",
|
"clipboard": "^2.0.11",
|
||||||
"jquery": "3",
|
"jquery": "3",
|
||||||
"js-cookie": "^3.0.1",
|
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"nanoid": "^4.0.0",
|
"nanoid": "^4.0.0",
|
||||||
"randomcolor": "^0.6.2",
|
"randomcolor": "^0.6.2",
|
||||||
|
@ -15,6 +15,10 @@ export const toggleSoundEnabled = payload => async dispatch => {
|
|||||||
dispatch({ type: 'TOGGLE_SOUND_ENABLED', payload });
|
dispatch({ type: 'TOGGLE_SOUND_ENABLED', payload });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const togglePersistenceEnabled = payload => async dispatch => {
|
||||||
|
dispatch({ type: 'TOGGLE_PERSISTENCE_ENABLED', payload });
|
||||||
|
};
|
||||||
|
|
||||||
export const toggleNotificationEnabled = payload => async dispatch => {
|
export const toggleNotificationEnabled = payload => async dispatch => {
|
||||||
dispatch({ type: 'TOGGLE_NOTIFICATION_ENABLED', payload });
|
dispatch({ type: 'TOGGLE_NOTIFICATION_ENABLED', payload });
|
||||||
};
|
};
|
||||||
|
@ -100,7 +100,9 @@ class Home extends Component {
|
|||||||
<Settings
|
<Settings
|
||||||
roomId={this.props.roomId}
|
roomId={this.props.roomId}
|
||||||
toggleSoundEnabled={this.props.toggleSoundEnabled}
|
toggleSoundEnabled={this.props.toggleSoundEnabled}
|
||||||
|
togglePersistenceEnabled={this.props.togglePersistenceEnabled}
|
||||||
soundIsEnabled={this.props.soundIsEnabled}
|
soundIsEnabled={this.props.soundIsEnabled}
|
||||||
|
persistenceIsEnabled={this.props.persistenceIsEnabled}
|
||||||
toggleNotificationEnabled={this.props.toggleNotificationEnabled}
|
toggleNotificationEnabled={this.props.toggleNotificationEnabled}
|
||||||
toggleNotificationAllowed={this.props.toggleNotificationAllowed}
|
toggleNotificationAllowed={this.props.toggleNotificationAllowed}
|
||||||
notificationIsEnabled={this.props.notificationIsEnabled}
|
notificationIsEnabled={this.props.notificationIsEnabled}
|
||||||
@ -151,7 +153,7 @@ class Home extends Component {
|
|||||||
|
|
||||||
createUser() {
|
createUser() {
|
||||||
return new Promise(async resolve => {
|
return new Promise(async resolve => {
|
||||||
const username = nanoid();
|
const username = this.props.username || nanoid();
|
||||||
|
|
||||||
const encryptDecryptKeys = await crypto.createEncryptDecryptKeys();
|
const encryptDecryptKeys = await crypto.createEncryptDecryptKeys();
|
||||||
const exportedEncryptDecryptPrivateKey = await crypto.exportKey(encryptDecryptKeys.privateKey);
|
const exportedEncryptDecryptPrivateKey = await crypto.exportKey(encryptDecryptKeys.privateKey);
|
||||||
@ -238,7 +240,7 @@ Home.propTypes = {
|
|||||||
username: PropTypes.string.isRequired,
|
username: PropTypes.string.isRequired,
|
||||||
publicKey: PropTypes.object.isRequired,
|
publicKey: PropTypes.object.isRequired,
|
||||||
members: PropTypes.array.isRequired,
|
members: PropTypes.array.isRequired,
|
||||||
socketId: PropTypes.object.isRequired,
|
socketId: PropTypes.string.isRequired,
|
||||||
roomId: PropTypes.string.isRequired,
|
roomId: PropTypes.string.isRequired,
|
||||||
roomLocked: PropTypes.bool.isRequired,
|
roomLocked: PropTypes.bool.isRequired,
|
||||||
modalComponent: PropTypes.string,
|
modalComponent: PropTypes.string,
|
||||||
@ -249,7 +251,9 @@ Home.propTypes = {
|
|||||||
toggleWindowFocus: PropTypes.func.isRequired,
|
toggleWindowFocus: PropTypes.func.isRequired,
|
||||||
faviconCount: PropTypes.number.isRequired,
|
faviconCount: PropTypes.number.isRequired,
|
||||||
soundIsEnabled: PropTypes.bool.isRequired,
|
soundIsEnabled: PropTypes.bool.isRequired,
|
||||||
|
persistenceIsEnabled: PropTypes.bool.isRequired,
|
||||||
toggleSoundEnabled: PropTypes.func.isRequired,
|
toggleSoundEnabled: PropTypes.func.isRequired,
|
||||||
|
togglePersistenceEnabled: PropTypes.func.isRequired,
|
||||||
notificationIsEnabled: PropTypes.bool.isRequired,
|
notificationIsEnabled: PropTypes.bool.isRequired,
|
||||||
notificationIsAllowed: PropTypes.bool,
|
notificationIsAllowed: PropTypes.bool,
|
||||||
toggleNotificationEnabled: PropTypes.func.isRequired,
|
toggleNotificationEnabled: PropTypes.func.isRequired,
|
||||||
|
@ -95,7 +95,6 @@ const WithNewMessageNotification = WrappedComponent => {
|
|||||||
switch (Notification.permission) {
|
switch (Notification.permission) {
|
||||||
case 'granted':
|
case 'granted':
|
||||||
this.props.toggleNotificationAllowed(true);
|
this.props.toggleNotificationAllowed(true);
|
||||||
this.props.toggleNotificationEnabled(true);
|
|
||||||
break;
|
break;
|
||||||
case 'denied':
|
case 'denied':
|
||||||
this.props.toggleNotificationAllowed(false);
|
this.props.toggleNotificationAllowed(false);
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
closeModal,
|
closeModal,
|
||||||
toggleWindowFocus,
|
toggleWindowFocus,
|
||||||
toggleSoundEnabled,
|
toggleSoundEnabled,
|
||||||
|
togglePersistenceEnabled,
|
||||||
toggleNotificationEnabled,
|
toggleNotificationEnabled,
|
||||||
toggleNotificationAllowed,
|
toggleNotificationAllowed,
|
||||||
toggleSocketConnected,
|
toggleSocketConnected,
|
||||||
@ -36,6 +37,7 @@ const mapStateToProps = state => {
|
|||||||
iAmOwner: Boolean(me && me.isOwner),
|
iAmOwner: Boolean(me && me.isOwner),
|
||||||
faviconCount: state.app.unreadMessageCount,
|
faviconCount: state.app.unreadMessageCount,
|
||||||
soundIsEnabled: state.app.soundIsEnabled,
|
soundIsEnabled: state.app.soundIsEnabled,
|
||||||
|
persistenceIsEnabled: state.app.persistenceIsEnabled,
|
||||||
notificationIsEnabled: state.app.notificationIsEnabled,
|
notificationIsEnabled: state.app.notificationIsEnabled,
|
||||||
notificationIsAllowed: state.app.notificationIsAllowed,
|
notificationIsAllowed: state.app.notificationIsAllowed,
|
||||||
socketConnected: state.app.socketConnected,
|
socketConnected: state.app.socketConnected,
|
||||||
@ -51,6 +53,7 @@ const mapDispatchToProps = {
|
|||||||
closeModal,
|
closeModal,
|
||||||
toggleWindowFocus,
|
toggleWindowFocus,
|
||||||
toggleSoundEnabled,
|
toggleSoundEnabled,
|
||||||
|
togglePersistenceEnabled,
|
||||||
toggleNotificationEnabled,
|
toggleNotificationEnabled,
|
||||||
toggleNotificationAllowed,
|
toggleNotificationAllowed,
|
||||||
toggleSocketConnected,
|
toggleSocketConnected,
|
||||||
|
@ -42,6 +42,21 @@ exports[`Settings component > should display 1`] = `
|
|||||||
Desktop Notification
|
Desktop Notification
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
class="form-check"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="form-check-label"
|
||||||
|
for="persistence-control"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="form-check-input"
|
||||||
|
id="persistence-control"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
Persist configuration
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
@ -112,6 +127,11 @@ exports[`Settings component > should display 1`] = `
|
|||||||
>
|
>
|
||||||
Русский
|
Русский
|
||||||
</option>
|
</option>
|
||||||
|
<option
|
||||||
|
value="pl"
|
||||||
|
>
|
||||||
|
Polish
|
||||||
|
</option>
|
||||||
<option
|
<option
|
||||||
value="zhCN"
|
value="zhCN"
|
||||||
>
|
>
|
||||||
@ -233,6 +253,21 @@ exports[`Settings component > should display 2`] = `
|
|||||||
Desktop notifications have been denied
|
Desktop notifications have been denied
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
class="form-check"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="form-check-label"
|
||||||
|
for="persistence-control"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="form-check-input"
|
||||||
|
id="persistence-control"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
Persist configuration
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
@ -303,6 +338,11 @@ exports[`Settings component > should display 2`] = `
|
|||||||
>
|
>
|
||||||
Русский
|
Русский
|
||||||
</option>
|
</option>
|
||||||
|
<option
|
||||||
|
value="pl"
|
||||||
|
>
|
||||||
|
Polish
|
||||||
|
</option>
|
||||||
<option
|
<option
|
||||||
value="zhCN"
|
value="zhCN"
|
||||||
>
|
>
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Cookie from 'js-cookie';
|
|
||||||
|
|
||||||
import RoomLink from '@/components/RoomLink';
|
import RoomLink from '@/components/RoomLink';
|
||||||
import T from '@/components/T';
|
import T from '@/components/T';
|
||||||
@ -12,6 +11,10 @@ class Settings extends Component {
|
|||||||
this.props.toggleSoundEnabled(!this.props.soundIsEnabled);
|
this.props.toggleSoundEnabled(!this.props.soundIsEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handlePersistenceToggle() {
|
||||||
|
this.props.togglePersistenceEnabled(!this.props.persistenceIsEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
handleNotificationToggle() {
|
handleNotificationToggle() {
|
||||||
Notification.requestPermission().then(permission => {
|
Notification.requestPermission().then(permission => {
|
||||||
if (permission === 'granted') {
|
if (permission === 'granted') {
|
||||||
@ -26,7 +29,6 @@ class Settings extends Component {
|
|||||||
|
|
||||||
handleLanguageChange(evt) {
|
handleLanguageChange(evt) {
|
||||||
const language = evt.target.value;
|
const language = evt.target.value;
|
||||||
Cookie.set('language', language);
|
|
||||||
this.props.setLanguage(language);
|
this.props.setLanguage(language);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,6 +70,18 @@ class Settings extends Component {
|
|||||||
{this.props.notificationIsAllowed === false && <T path="desktopNotificationBlocked" />}
|
{this.props.notificationIsAllowed === false && <T path="desktopNotificationBlocked" />}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="form-check">
|
||||||
|
<label className="form-check-label" htmlFor="persistence-control">
|
||||||
|
<input
|
||||||
|
id="persistence-control"
|
||||||
|
onChange={this.handlePersistenceToggle.bind(this)}
|
||||||
|
className="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
checked={this.props.persistenceIsEnabled}
|
||||||
|
/>
|
||||||
|
<T path="persistence" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@ -105,6 +119,7 @@ class Settings extends Component {
|
|||||||
<option value="nl">Nederlands</option>
|
<option value="nl">Nederlands</option>
|
||||||
<option value="it">Italiano</option>
|
<option value="it">Italiano</option>
|
||||||
<option value="ru">Русский</option>
|
<option value="ru">Русский</option>
|
||||||
|
<option value="pl">Polish</option>
|
||||||
<option value="zhCN">中文</option>
|
<option value="zhCN">中文</option>
|
||||||
<option value="ja">日本語</option>
|
<option value="ja">日本語</option>
|
||||||
<option value="tr">Türkçe</option>
|
<option value="tr">Türkçe</option>
|
||||||
@ -170,6 +185,7 @@ class Settings extends Component {
|
|||||||
|
|
||||||
Settings.propTypes = {
|
Settings.propTypes = {
|
||||||
soundIsEnabled: PropTypes.bool.isRequired,
|
soundIsEnabled: PropTypes.bool.isRequired,
|
||||||
|
persistenceIsEnabled: PropTypes.bool.isRequired,
|
||||||
toggleSoundEnabled: PropTypes.func.isRequired,
|
toggleSoundEnabled: PropTypes.func.isRequired,
|
||||||
notificationIsEnabled: PropTypes.bool.isRequired,
|
notificationIsEnabled: PropTypes.bool.isRequired,
|
||||||
notificationIsAllowed: PropTypes.bool,
|
notificationIsAllowed: PropTypes.bool,
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
"lists all commands"
|
"lists all commands"
|
||||||
],
|
],
|
||||||
"sound": "Sound",
|
"sound": "Sound",
|
||||||
|
"persistence": "Persist configuration",
|
||||||
"newMessageNotification": "New message notification",
|
"newMessageNotification": "New message notification",
|
||||||
"desktopNotification": "Desktop Notification",
|
"desktopNotification": "Desktop Notification",
|
||||||
"desktopNotificationBlocked": "Desktop notifications have been denied",
|
"desktopNotificationBlocked": "Desktop notifications have been denied",
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
"lister toutes les commandes"
|
"lister toutes les commandes"
|
||||||
],
|
],
|
||||||
"sound": "Son",
|
"sound": "Son",
|
||||||
|
"persistence": "Mémoriser la configuration",
|
||||||
"newMessageNotification": "Notification lors d'un nouveau message",
|
"newMessageNotification": "Notification lors d'un nouveau message",
|
||||||
"desktopNotification": "Notification Système",
|
"desktopNotification": "Notification Système",
|
||||||
"desktopNotificationBlocked": "Les notifications systèmes ont été refusée",
|
"desktopNotificationBlocked": "Les notifications systèmes ont été refusée",
|
||||||
|
@ -10,6 +10,7 @@ import esAR from './es-AR';
|
|||||||
import ja from './ja';
|
import ja from './ja';
|
||||||
import tr from './tr';
|
import tr from './tr';
|
||||||
import ko from './ko';
|
import ko from './ko';
|
||||||
|
import pl from './pl';
|
||||||
|
|
||||||
const languagesMap = {
|
const languagesMap = {
|
||||||
en,
|
en,
|
||||||
@ -24,6 +25,7 @@ const languagesMap = {
|
|||||||
ja,
|
ja,
|
||||||
tr,
|
tr,
|
||||||
ko,
|
ko,
|
||||||
|
pl
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
41
client/src/i18n/pl.json
Normal file
41
client/src/i18n/pl.json
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"newRoomButton": "Nowy pokój",
|
||||||
|
"lockedRoom": "{username} zablokował pokój",
|
||||||
|
"unlockedRoom": "{username} odblokował pokój",
|
||||||
|
"agreement": "Korzystając z darkwire, zgadzasz się na naszą politykę zasad korzystania i warunki korzystania",
|
||||||
|
"typePlaceholder": "Pisz tutaj",
|
||||||
|
"aboutButton": "O nas",
|
||||||
|
"settingsButton": "Ustawienia",
|
||||||
|
"settings": "Ustawienia",
|
||||||
|
"aboutHeader": "O nas",
|
||||||
|
"copyButtonTooltip": "Skopiowano",
|
||||||
|
"welcomeHeader": "Witamy na czacie Starymisiada",
|
||||||
|
"sentFile": "Wysłałeś {filename}",
|
||||||
|
"userJoined": "{username} dołączył",
|
||||||
|
"userLeft": "{username} opuścił",
|
||||||
|
"userSentFile": "{username} wysłał Ci plik.",
|
||||||
|
"downloadFile": "Pobierz {filename}",
|
||||||
|
"nameChange": "{oldUsername} zmienił swoje imię na {newUsername}",
|
||||||
|
"settingsHeader": "Ustawienia & Pomoc",
|
||||||
|
"copyRoomHeader": "Ten pokój",
|
||||||
|
"languageDropdownHeader": "Język",
|
||||||
|
"roomOwnerHeader": "Własność pokoju",
|
||||||
|
"roomOwnerText": "Osoba, która utworzyła pokój, jest właścicielem pokoju i ma specjalne uprawnienia, takie jak możliwość zablokowania i odblokowania pokoju. Jeśli właściciel opuści pokój, druga osoba, która dołączy, przejmuje własność. Jeśli ona opuści, trzecia osoba staje się właścicielem itd. Właściciel pokoju ma ikonę gwiazdki obok swojej nazwy użytkownika w rozwijanej liście uczestników.",
|
||||||
|
"lockRoomHeader": "Zablokuj pokój",
|
||||||
|
"lockRoomText": "Jeśli jesteś właścicielem pokoju, możesz zablokować i odblokować pokój, klikając ikonę zamka w pasku nawigacyjnym. Gdy pokój jest zablokowany, żadna inna osoba nie może do niego dołączyć.",
|
||||||
|
"slashCommandsHeader": "Komendy",
|
||||||
|
"slashCommandsText": "Dostępne są następujące komendy:",
|
||||||
|
"slashCommandsBullets": [
|
||||||
|
"zmienia nazwę użytkownika",
|
||||||
|
"wykonuje akcję",
|
||||||
|
"czyści historię wiadomości",
|
||||||
|
"wyświetla wszystkie komendy"
|
||||||
|
],
|
||||||
|
"sound": "Dźwięk",
|
||||||
|
"newMessageNotification": "Powiadomienie o nowej wiadomości",
|
||||||
|
"desktopNotification": "Powiadomienie na pulpicie",
|
||||||
|
"desktopNotificationBlocked": "Powiadomienia na pulpicie zostały wyłączone",
|
||||||
|
"welcomeModalCTA": "Ok",
|
||||||
|
"lockedRoomHeader": "Ten pokój jest zablokowany",
|
||||||
|
"helpTranslate": "Pomóż nam przetłumaczyć darkwire!"
|
||||||
|
}
|
@ -12,8 +12,10 @@ import 'bootstrap/dist/js/bootstrap.bundle.min.js';
|
|||||||
import configureStore from '@/store/';
|
import configureStore from '@/store/';
|
||||||
import Home from '@/components/Home/';
|
import Home from '@/components/Home/';
|
||||||
import { hasTouchSupport } from '@/utils/dom';
|
import { hasTouchSupport } from '@/utils/dom';
|
||||||
|
import { loadPersistedState, persistState } from '@/utils/persistence';
|
||||||
|
|
||||||
const store = configureStore();
|
const store = configureStore(loadPersistedState());
|
||||||
|
store.subscribe(() => persistState(store));
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import Cookie from 'js-cookie';
|
|
||||||
|
|
||||||
import { getTranslations } from '@/i18n';
|
import { getTranslations } from '@/i18n';
|
||||||
|
|
||||||
const language = Cookie.get('language') || navigator.language || 'en';
|
const language = navigator.language || 'en';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
modalComponent: null,
|
modalComponent: null,
|
||||||
@ -10,14 +8,17 @@ const initialState = {
|
|||||||
windowIsFocused: true,
|
windowIsFocused: true,
|
||||||
unreadMessageCount: 0,
|
unreadMessageCount: 0,
|
||||||
soundIsEnabled: true,
|
soundIsEnabled: true,
|
||||||
notificationIsEnabled: false,
|
persistenceIsEnabled: false,
|
||||||
|
notificationIsEnabled: true,
|
||||||
notificationIsAllowed: null,
|
notificationIsAllowed: null,
|
||||||
socketConnected: false,
|
socketConnected: false,
|
||||||
language,
|
language,
|
||||||
translations: getTranslations(language),
|
translations: getTranslations(language),
|
||||||
};
|
};
|
||||||
|
|
||||||
const app = (state = initialState, action) => {
|
const app = (receivedState, action) => {
|
||||||
|
const state = { ...initialState, ...receivedState };
|
||||||
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'OPEN_MODAL':
|
case 'OPEN_MODAL':
|
||||||
return {
|
return {
|
||||||
@ -50,6 +51,11 @@ const app = (state = initialState, action) => {
|
|||||||
...state,
|
...state,
|
||||||
soundIsEnabled: action.payload,
|
soundIsEnabled: action.payload,
|
||||||
};
|
};
|
||||||
|
case 'TOGGLE_PERSISTENCE_ENABLED':
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
persistenceIsEnabled: action.payload,
|
||||||
|
};
|
||||||
case 'TOGGLE_NOTIFICATION_ENABLED':
|
case 'TOGGLE_NOTIFICATION_ENABLED':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
@ -16,9 +16,10 @@ describe('App reducer', () => {
|
|||||||
modalComponent: null,
|
modalComponent: null,
|
||||||
scrolledToBottom: true,
|
scrolledToBottom: true,
|
||||||
socketConnected: false,
|
socketConnected: false,
|
||||||
notificationIsEnabled: false,
|
notificationIsEnabled: true,
|
||||||
notificationIsAllowed: null,
|
notificationIsAllowed: null,
|
||||||
soundIsEnabled: true,
|
soundIsEnabled: true,
|
||||||
|
persistenceIsEnabled: false,
|
||||||
translations: { path: 'test' },
|
translations: { path: 'test' },
|
||||||
unreadMessageCount: 0,
|
unreadMessageCount: 0,
|
||||||
windowIsFocused: true,
|
windowIsFocused: true,
|
||||||
@ -26,51 +27,65 @@ describe('App reducer', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should handle OPEN_MODAL', () => {
|
it('should handle OPEN_MODAL', () => {
|
||||||
expect(reducer({}, { type: 'OPEN_MODAL', payload: 'test' })).toEqual({ modalComponent: 'test' });
|
expect(reducer({}, { type: 'OPEN_MODAL', payload: 'test' })).toEqual(
|
||||||
|
expect.objectContaining({ modalComponent: 'test' }),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle CLOSE_MODAL', () => {
|
it('should handle CLOSE_MODAL', () => {
|
||||||
expect(reducer({}, { type: 'CLOSE_MODAL' })).toEqual({ modalComponent: null });
|
expect(reducer({}, { type: 'CLOSE_MODAL' })).toEqual(expect.objectContaining({ modalComponent: null }));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle SET_SCROLLED_TO_BOTTOM', () => {
|
it('should handle SET_SCROLLED_TO_BOTTOM', () => {
|
||||||
expect(reducer({}, { type: 'SET_SCROLLED_TO_BOTTOM', payload: true })).toEqual({ scrolledToBottom: true });
|
expect(reducer({}, { type: 'SET_SCROLLED_TO_BOTTOM', payload: true })).toEqual(
|
||||||
expect(reducer({}, { type: 'SET_SCROLLED_TO_BOTTOM', payload: false })).toEqual({ scrolledToBottom: false });
|
expect.objectContaining({ scrolledToBottom: true }),
|
||||||
|
);
|
||||||
|
expect(reducer({}, { type: 'SET_SCROLLED_TO_BOTTOM', payload: false })).toEqual(
|
||||||
|
expect.objectContaining({ scrolledToBottom: false }),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle TOGGLE_WINDOW_FOCUS', () => {
|
it('should handle TOGGLE_WINDOW_FOCUS', () => {
|
||||||
expect(reducer({ unreadMessageCount: 10 }, { type: 'TOGGLE_WINDOW_FOCUS', payload: true })).toEqual({
|
expect(reducer({ unreadMessageCount: 10 }, { type: 'TOGGLE_WINDOW_FOCUS', payload: true })).toEqual(
|
||||||
windowIsFocused: true,
|
expect.objectContaining({
|
||||||
unreadMessageCount: 0,
|
windowIsFocused: true,
|
||||||
});
|
unreadMessageCount: 0,
|
||||||
|
}),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle RECEIVE_ENCRYPTED_MESSAGE_TEXT_MESSAGE', () => {
|
it('should handle RECEIVE_ENCRYPTED_MESSAGE_TEXT_MESSAGE', () => {
|
||||||
expect(
|
expect(
|
||||||
reducer({ unreadMessageCount: 10, windowIsFocused: false }, { type: 'RECEIVE_ENCRYPTED_MESSAGE_TEXT_MESSAGE' }),
|
reducer({ unreadMessageCount: 10, windowIsFocused: false }, { type: 'RECEIVE_ENCRYPTED_MESSAGE_TEXT_MESSAGE' }),
|
||||||
).toEqual({ unreadMessageCount: 11, windowIsFocused: false });
|
).toEqual(expect.objectContaining({ unreadMessageCount: 11, windowIsFocused: false }));
|
||||||
expect(
|
expect(
|
||||||
reducer({ unreadMessageCount: 10, windowIsFocused: true }, { type: 'RECEIVE_ENCRYPTED_MESSAGE_TEXT_MESSAGE' }),
|
reducer({ unreadMessageCount: 10, windowIsFocused: true }, { type: 'RECEIVE_ENCRYPTED_MESSAGE_TEXT_MESSAGE' }),
|
||||||
).toEqual({ unreadMessageCount: 0, windowIsFocused: true });
|
).toEqual(expect.objectContaining({ unreadMessageCount: 0, windowIsFocused: true }));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle TOGGLE_SOUND_ENABLED', () => {
|
it('should handle TOGGLE_SOUND_ENABLED', () => {
|
||||||
expect(reducer({}, { type: 'TOGGLE_SOUND_ENABLED', payload: true })).toEqual({
|
expect(reducer({}, { type: 'TOGGLE_SOUND_ENABLED', payload: true })).toEqual(
|
||||||
soundIsEnabled: true,
|
expect.objectContaining({
|
||||||
});
|
soundIsEnabled: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle TOGGLE_SOCKET_CONNECTED', () => {
|
it('should handle TOGGLE_SOCKET_CONNECTED', () => {
|
||||||
expect(reducer({}, { type: 'TOGGLE_SOCKET_CONNECTED', payload: true })).toEqual({
|
expect(reducer({}, { type: 'TOGGLE_SOCKET_CONNECTED', payload: true })).toEqual(
|
||||||
socketConnected: true,
|
expect.objectContaining({
|
||||||
});
|
socketConnected: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle CHANGE_LANGUAGE', () => {
|
it('should handle CHANGE_LANGUAGE', () => {
|
||||||
getTranslations.mockReturnValueOnce({ path: 'new lang' });
|
getTranslations.mockReturnValueOnce({ path: 'new lang' });
|
||||||
expect(reducer({}, { type: 'CHANGE_LANGUAGE', payload: 'fr' })).toEqual({
|
expect(reducer({}, { type: 'CHANGE_LANGUAGE', payload: 'fr' })).toEqual(
|
||||||
language: 'fr',
|
expect.objectContaining({
|
||||||
translations: { path: 'new lang' },
|
language: 'fr',
|
||||||
});
|
translations: { path: 'new lang' },
|
||||||
|
}),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -5,7 +5,9 @@ const initialState = {
|
|||||||
id: '',
|
id: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
const user = (state = initialState, action) => {
|
const user = (receivedState, action) => {
|
||||||
|
const state = { ...initialState, ...receivedState };
|
||||||
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'CREATE_USER':
|
case 'CREATE_USER':
|
||||||
return {
|
return {
|
||||||
|
@ -25,6 +25,9 @@ describe('User reducer', () => {
|
|||||||
it('should handle SEND_ENCRYPTED_MESSAGE_CHANGE_USERNAME', () => {
|
it('should handle SEND_ENCRYPTED_MESSAGE_CHANGE_USERNAME', () => {
|
||||||
const payload = { newUsername: 'alice' };
|
const payload = { newUsername: 'alice' };
|
||||||
expect(reducer({ username: 'polux' }, { type: 'SEND_ENCRYPTED_MESSAGE_CHANGE_USERNAME', payload })).toEqual({
|
expect(reducer({ username: 'polux' }, { type: 'SEND_ENCRYPTED_MESSAGE_CHANGE_USERNAME', payload })).toEqual({
|
||||||
|
id: '',
|
||||||
|
privateKey: {},
|
||||||
|
publicKey: {},
|
||||||
username: 'alice',
|
username: 'alice',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
|
||||||
import 'react-simple-dropdown/styles/Dropdown.css';
|
|
||||||
import 'stylesheets/app.sass';
|
|
||||||
import 'bootstrap/dist/js/bootstrap.bundle.min.js';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import { Redirect } from 'react-router';
|
|
||||||
import { Provider } from 'react-redux';
|
|
||||||
import configureStore from './store';
|
|
||||||
import { BrowserRouter, Route, Switch } from 'react-router-dom';
|
|
||||||
import shortId from 'shortid';
|
|
||||||
import Home from 'components/Home';
|
|
||||||
import { hasTouchSupport } from './utils/dom';
|
|
||||||
|
|
||||||
const store = configureStore();
|
|
||||||
|
|
||||||
export default class Root extends Component {
|
|
||||||
componentWillMount() {
|
|
||||||
if (hasTouchSupport) {
|
|
||||||
document.body.classList.add('touch');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<Provider store={store}>
|
|
||||||
<BrowserRouter>
|
|
||||||
<div className="h-100">
|
|
||||||
<Switch>
|
|
||||||
<Route exact path="/" render={() => <Redirect to={`/${shortId.generate()}`} />} />
|
|
||||||
<Route path="/:roomId" component={Home} />
|
|
||||||
</Switch>
|
|
||||||
</div>
|
|
||||||
</BrowserRouter>
|
|
||||||
</Provider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
83
client/src/utils/persistence.js
Normal file
83
client/src/utils/persistence.js
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
/**
|
||||||
|
* Handle localStorage persistence
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { debounce } from 'lodash';
|
||||||
|
import { getTranslations } from '@/i18n';
|
||||||
|
|
||||||
|
const getSettings = () => {
|
||||||
|
try {
|
||||||
|
return JSON.parse(localStorage.getItem('settings')) || {};
|
||||||
|
} catch (e) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const setSettings = settings => {
|
||||||
|
localStorage.setItem('settings', JSON.stringify(settings));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const loadPersistedState = () => {
|
||||||
|
const state = {};
|
||||||
|
|
||||||
|
const stored = getSettings();
|
||||||
|
|
||||||
|
if (stored.persistenceIsEnabled !== true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.app = {};
|
||||||
|
if (stored.language) {
|
||||||
|
state.app.language = stored.language;
|
||||||
|
state.app.translations = getTranslations(stored.language);
|
||||||
|
}
|
||||||
|
state.user = {};
|
||||||
|
if (stored.username) {
|
||||||
|
state.user.username = stored.username;
|
||||||
|
}
|
||||||
|
state.app.soundIsEnabled = stored.soundIsEnabled !== false;
|
||||||
|
state.app.persistenceIsEnabled = stored.persistenceIsEnabled === true;
|
||||||
|
state.app.notificationIsEnabled = stored.notificationIsEnabled !== false;
|
||||||
|
return state;
|
||||||
|
};
|
||||||
|
|
||||||
|
let prevState;
|
||||||
|
|
||||||
|
export const persistState = debounce(async store => {
|
||||||
|
const state = store.getState();
|
||||||
|
|
||||||
|
// We need prev state to compare
|
||||||
|
if (prevState) {
|
||||||
|
const {
|
||||||
|
user: { username },
|
||||||
|
app: { notificationIsEnabled, soundIsEnabled, persistenceIsEnabled, language },
|
||||||
|
} = state;
|
||||||
|
|
||||||
|
|
||||||
|
if (!persistenceIsEnabled) {
|
||||||
|
setSettings({ persistenceIsEnabled: false });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stored = getSettings();
|
||||||
|
|
||||||
|
if (prevState.user.notificationIsEnabled !== notificationIsEnabled) {
|
||||||
|
stored.notificationIsEnabled = notificationIsEnabled;
|
||||||
|
}
|
||||||
|
if (prevState.app.soundIsEnabled !== soundIsEnabled) {
|
||||||
|
stored.soundIsEnabled = soundIsEnabled;
|
||||||
|
}
|
||||||
|
if (prevState.app.persistenceIsEnabled !== persistenceIsEnabled) {
|
||||||
|
stored.persistenceIsEnabled = persistenceIsEnabled;
|
||||||
|
}
|
||||||
|
if (prevState.user.username !== username && username) {
|
||||||
|
stored.username = username;
|
||||||
|
}
|
||||||
|
if (prevState.app.language !== language && language) {
|
||||||
|
stored.language = language;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSettings(stored);
|
||||||
|
}
|
||||||
|
prevState = JSON.parse(JSON.stringify(state));
|
||||||
|
}, 1000);
|
@ -2185,11 +2185,6 @@ jquery@3:
|
|||||||
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.1.tgz#fab0408f8b45fc19f956205773b62b292c147a16"
|
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.1.tgz#fab0408f8b45fc19f956205773b62b292c147a16"
|
||||||
integrity sha512-opJeO4nCucVnsjiXOE+/PcCgYw9Gwpvs/a6B1LL/lQhwWwpbVEVYDZ1FokFr8PRc7ghYlrFPuyHuiiDNTQxmcw==
|
integrity sha512-opJeO4nCucVnsjiXOE+/PcCgYw9Gwpvs/a6B1LL/lQhwWwpbVEVYDZ1FokFr8PRc7ghYlrFPuyHuiiDNTQxmcw==
|
||||||
|
|
||||||
js-cookie@^3.0.1:
|
|
||||||
version "3.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.1.tgz#9e39b4c6c2f56563708d7d31f6f5f21873a92414"
|
|
||||||
integrity sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw==
|
|
||||||
|
|
||||||
js-sdsl@^4.1.4:
|
js-sdsl@^4.1.4:
|
||||||
version "4.2.0"
|
version "4.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.2.0.tgz#278e98b7bea589b8baaf048c20aeb19eb7ad09d0"
|
resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.2.0.tgz#278e98b7bea589b8baaf048c20aeb19eb7ad09d0"
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
"koa-router": "^7.2.1",
|
"koa-router": "^7.2.1",
|
||||||
"koa-send": "^5.0.0",
|
"koa-send": "^5.0.0",
|
||||||
"koa-static": "^5.0.0",
|
"koa-static": "^5.0.0",
|
||||||
"lodash": "^4.17.20",
|
"lodash": "^4.17.21",
|
||||||
"mailgun-js": "^0.22.0",
|
"mailgun-js": "^0.22.0",
|
||||||
"moment": "^2.18.1",
|
"moment": "^2.18.1",
|
||||||
"redis": "^4.5.1",
|
"redis": "^4.5.1",
|
||||||
|
@ -2782,10 +2782,10 @@ lodash.merge@^4.6.2:
|
|||||||
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
|
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
|
||||||
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
|
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
|
||||||
|
|
||||||
lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.20:
|
lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.21:
|
||||||
version "4.17.20"
|
version "4.17.21"
|
||||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||||
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
|
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||||
|
|
||||||
lru-cache@^4.1.2:
|
lru-cache@^4.1.2:
|
||||||
version "4.1.5"
|
version "4.1.5"
|
||||||
|
@ -214,9 +214,9 @@ locate-path@^3.0.0:
|
|||||||
path-exists "^3.0.0"
|
path-exists "^3.0.0"
|
||||||
|
|
||||||
lodash@^4.17.15:
|
lodash@^4.17.15:
|
||||||
version "4.17.20"
|
version "4.17.21"
|
||||||
resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz"
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||||
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
|
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||||
|
|
||||||
map-age-cleaner@^0.1.1:
|
map-age-cleaner@^0.1.1:
|
||||||
version "0.1.3"
|
version "0.1.3"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user