From 59c8407375f08eb546e20666659b3f0348be1c59 Mon Sep 17 00:00:00 2001 From: mxaln Date: Fri, 25 Oct 2019 12:11:50 -0400 Subject: [PATCH 1/3] added sorting function to available users based on frequency --- src/js/pages/Login/AvailableUsers.js | 25 +++++++++++++++++++---- src/js/pages/Login/components/UserCard.js | 5 ++++- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/js/pages/Login/AvailableUsers.js b/src/js/pages/Login/AvailableUsers.js index a15035f9..ffe21a50 100644 --- a/src/js/pages/Login/AvailableUsers.js +++ b/src/js/pages/Login/AvailableUsers.js @@ -13,6 +13,7 @@ export class AvailableUsers extends React.Component { constructor(props) { super(props); this.handleClick = this.handleClick.bind(this); + this.sortedUsers = this.sortedUsers.bind(this); } handleClick(clickSrc) { @@ -30,12 +31,28 @@ export class AvailableUsers extends React.Component { if (language) { updateLanguage(language); } + } + sortedUsers() { + const {users} = this.props; + + users.forEach((element) => { + element["timestamp"] = localStorage.getItem("login:" + element.icon_hash); + }); + + users.sort((a, b) => { + if(a.timestamp == null) return 1; + if(b.timestamp == null) return -1; + + return b.timestamp - a.timestamp; + }); + + return users; } render() { - const {users, txt} = this.props; + const {txt} = this.props; return ( @@ -60,13 +77,13 @@ export class AvailableUsers extends React.Component { { - users.length>0? users.map((user,index) => { + this.sortedUsers().length > 0? this.sortedUsers().map((user,index) => { return ( user.is_social? '' : - + - );}) : '' + );}) : '' } diff --git a/src/js/pages/Login/components/UserCard.js b/src/js/pages/Login/components/UserCard.js index 56b298aa..060e8427 100644 --- a/src/js/pages/Login/components/UserCard.js +++ b/src/js/pages/Login/components/UserCard.js @@ -23,7 +23,10 @@ export default class UserCard extends React.Component { } identLogin(hash) { - this.props.identiconLogin(hash, ()=>this.props.history.push('/projects')); + this.props.identiconLogin(hash, ()=>{ + localStorage.setItem("login:" + hash, Date.now()); + this.props.history.push('/projects'); + }); } play() { From 61e9e02af8ecd3ead946b8ca39338c50025733ba Mon Sep 17 00:00:00 2001 From: mxaln Date: Tue, 29 Oct 2019 08:30:44 -0400 Subject: [PATCH 2/3] Added ability to change api server name --- README.md | 2 +- package.json | 8 +- public/index.html | 2 +- sonar-project.properties | 4 +- src/App.js | 16 +- .../unit_tests/pages/Login/LoginPage.test.js | 7 +- .../pages/Settings/SettingsPage.test.js | 31 ++++ src/config/config.js | 29 +--- src/js/actions/ProjectsPageActions.js | 3 +- src/js/actions/UserActions.js | 2 +- src/js/components/NavBar.js | 6 +- src/js/pages/Login/LoginPage.js | 26 ++- .../Login/components/WelcomeComponent.js | 2 +- src/js/pages/Settings/SettingsPage.js | 163 ++++++++++++++++++ src/languages/textToDisplay.json | 27 ++- src/views/index.ejs | 2 +- 16 files changed, 280 insertions(+), 50 deletions(-) create mode 100644 src/__test__/unit_tests/pages/Settings/SettingsPage.test.js create mode 100644 src/js/pages/Settings/SettingsPage.js diff --git a/README.md b/README.md index 1543b51a..c65fde97 100644 --- a/README.md +++ b/README.md @@ -6,4 +6,4 @@ # translationExchange Manage and review development project. Started by the 8WoC 2017 internship team and continued by Wycliffe Associates. -Translation Exchange is a system for oral translators and narrators to securely store their audio files and to invite community checking and review. Translation Exchange is designed for the realities of the developing world: It works 100% off-line via local wifi network and can be run on low-power, low-cost computing devices such as Raspberry Pi. It maintains the same design philosophy as Translation Recorder: simple use, minimal text interface, and attention to audio quality. +BTT Exchanger is a system for oral translators and narrators to securely store their audio files and to invite community checking and review. BTT Exchanger is designed for the realities of the developing world: It works 100% off-line via local wifi network and can be run on low-power, low-cost computing devices such as Raspberry Pi. It maintains the same design philosophy as Translation Recorder: simple use, minimal text interface, and attention to audio quality. diff --git a/package.json b/package.json index 51b8bf35..60b487e7 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { - "name": "translationexchange", - "author": "mxaln", - "version": "1.1.7", - "description": "Translation Exchange", + "name": "bttexchanger", + "author": "WA", + "version": "1.1.8", + "description": "BTT Exchanger", "private": true, "homepage": ".", "main": "src/start.js", diff --git a/public/index.html b/public/index.html index c9643faf..a1b25b5a 100644 --- a/public/index.html +++ b/public/index.html @@ -22,7 +22,7 @@ work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> - Translation eXchange + BTT Exchanger diff --git a/sonar-project.properties b/sonar-project.properties index 33a2518f..efc632aa 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,5 +1,5 @@ -sonar.projectKey=translationexchange -sonar.projectName=Translation Exchange +sonar.projectKey=bttexchanger +sonar.projectName=BTT Exchanger sonar.projectVersion=1.0 sonar.sources=src sonar.exclusions=src/js/*.js src/css src/config src/assets src/fonts src/languages src/views src/*.js src/*.css diff --git a/src/App.js b/src/App.js index f7fd19ba..7c3daf35 100644 --- a/src/App.js +++ b/src/App.js @@ -7,7 +7,8 @@ import {ToastContainer} from 'react-toastify'; import NotFound from './js/pages/NotFound'; import axios from 'axios'; import Welcome from './js/pages/Login/LoginPage.js'; -import Download from './js/pages/Download/DownloadPage.js'; +import DownloadPage from './js/pages/Download/DownloadPage.js'; +import SettingsPage from './js/pages/Settings/SettingsPage.js'; import AvailableUsers from './js/pages/Login/AvailableUsers.js'; import ErrorPage from './js/pages/ErrorPage/ErrorPage'; import CreateUserContainer from './js/pages/user/components/CreateUserContainer'; @@ -23,7 +24,11 @@ import config from './config/config'; // import and configure the raven client for sentry in order to track errors import Raven from 'raven-js'; -Raven.config(`http://9183fe1745da4049889061d44d154a4b@${config.domain}:9000/3`).install(); +try { + Raven.config(`http://9183fe1745da4049889061d44d154a4b@${config.domain}:9000/3`).install(); +} catch(error) { + console.log("Raven is not configured!"); +} class App extends Component { @@ -33,7 +38,7 @@ class App extends Component { //configuration for web requests axios.defaults.timeout = 120000; - this.state ={ + this.state = { hasError: false, }; } @@ -43,8 +48,6 @@ class App extends Component { Raven.captureException(error, {extra: info}); //send error to raven client } - - render() { return ( /* @@ -61,7 +64,8 @@ class App extends Component { - + + diff --git a/src/__test__/unit_tests/pages/Login/LoginPage.test.js b/src/__test__/unit_tests/pages/Login/LoginPage.test.js index 57ee0407..d7acb05a 100644 --- a/src/__test__/unit_tests/pages/Login/LoginPage.test.js +++ b/src/__test__/unit_tests/pages/Login/LoginPage.test.js @@ -4,6 +4,7 @@ import {Welcome} from '../../../../js/pages/Login/LoginPage'; import {shallow} from 'enzyme'; const mockProps = { + history: [], txt: { loading: false, availableUsers: 'availableUsers', @@ -18,12 +19,16 @@ describe('LoginPage test suite', () => { expect(wrapper.find('LoginPage').length).toBe(1); expect(wrapper.find('Language').length).toBe(1); expect(wrapper.find('LanguageContainer').length).toBe(1); - + expect(wrapper.find('SettingsButton').length).toBe(1); }); test('on Select function', () => { wrapper.instance().onSelect('language'); expect(mockProps.updateLanguage.mock.calls.length).toEqual(1); + }); + it('should navigate to settings', () => { + wrapper.instance().onSettingsClick(); + expect(wrapper.instance().props.history.length).toEqual(1); }); }); diff --git a/src/__test__/unit_tests/pages/Settings/SettingsPage.test.js b/src/__test__/unit_tests/pages/Settings/SettingsPage.test.js new file mode 100644 index 00000000..27b9523f --- /dev/null +++ b/src/__test__/unit_tests/pages/Settings/SettingsPage.test.js @@ -0,0 +1,31 @@ +/* global it describe expect jest*/ +import React from 'react'; +import {shallow} from 'enzyme'; +import {SettingsPage} from '../../../../js/pages/Settings/SettingsPage'; + +const mockProps = { + txt: { + serverName: 'Server name', + settings: 'settings', + goBack: 'go back', + }, + onServerNameChange: jest.fn(), +}; + +describe('Settings Page test suite', () => { + + const wrapper = shallow(); + + it('should render the component correctly', () => { + expect(wrapper.find('Container').length).toBe(1); + expect(wrapper.find('BackLink').length).toBe(1); + expect(wrapper.find('Header').length).toBe(1); + expect(wrapper.find('SettingsContainer').length).toBe(1); + expect(wrapper.find('ButtonsContainer').length).toBe(1); + expect(wrapper.find('SettingsItem').length).toBeGreaterThan(0); + expect(wrapper.find('SettingsTitle').length).toBeGreaterThan(0); + expect(wrapper.find('SettingsValue').length).toBeGreaterThan(0); + expect(wrapper.find('SettingsInput').length).toBeGreaterThan(0); + }); + +}); diff --git a/src/config/config.js b/src/config/config.js index efa0ffd0..3c486caa 100644 --- a/src/config/config.js +++ b/src/config/config.js @@ -4,30 +4,13 @@ Then you can access the settings as "config.apiUrl", "config.streamingUrl", etc */ -// var config = { -// apiUrl: "http://172.19.145.91/api/", -// streamingUrl: "http://172.19.145.91/" -// }; -// -// var config = { -// apiUrl: "http://127.0.0.1:8000/api/", -// streamingUrl: "http://127.0.0.1/" -// }; +const uploadServer = localStorage.getItem("uploadServer") || "opentranslationtools.org"; - - - -// const config = { // local development -// apiUrl: 'http://localhost/api/', -// streamingUrl: 'http://localhost/', -// redirectUri: 'http://localhost:3000', -// }; - -const config = { //production - apiUrl: 'http://opentranslationtools.org/api/', - streamingUrl: 'http://opentranslationtools.org/', - redirectUri: 'http://opentranslationtools.org/', - domain: 'opentranslationtools.org' +const config = { + apiUrl: 'http://' + uploadServer + '/api/', + streamingUrl: 'http://' + uploadServer + '/', + redirectUri: 'http://' + uploadServer + '/', + domain: '' + uploadServer + '' }; export default config; diff --git a/src/js/actions/ProjectsPageActions.js b/src/js/actions/ProjectsPageActions.js index 4efb2e34..035c1285 100644 --- a/src/js/actions/ProjectsPageActions.js +++ b/src/js/actions/ProjectsPageActions.js @@ -15,9 +15,10 @@ export const fetchAllProjects = (query, redirect) => { dispatch(dispatchAllProjectsFailed(err)); localStorage.removeItem('token'); - if(err.response.status == 401) { + if(err.response != undefined && err.response.status == 401) { redirect.push('/welcome'); } else { + console.log("ERROR: ", config.apiUrl, query); redirect.push('/errorPage'); } }); diff --git a/src/js/actions/UserActions.js b/src/js/actions/UserActions.js index 51ad1a30..66551fc5 100644 --- a/src/js/actions/UserActions.js +++ b/src/js/actions/UserActions.js @@ -10,7 +10,7 @@ return (dispatch) => { dispatch(fetchUserSuccess(response.data)); }) .catch(error => { - console.log(error); //TODO handle error + console.log("ERROR: ", config.apiUrl); //TODO handle error redirect.push('./ErrorPage'); }); }; diff --git a/src/js/components/NavBar.js b/src/js/components/NavBar.js index d38e25df..9ce8bc2f 100644 --- a/src/js/components/NavBar.js +++ b/src/js/components/NavBar.js @@ -53,6 +53,9 @@ class NavBar extends Component { if (key === '2') { this.props.history.push('./download'); } + if (key === '3') { + this.props.history.push('./settings'); + } } onSelect({key, item}) { @@ -91,6 +94,7 @@ class NavBar extends Component { {txt.logOut} {txt.progressPage} {txt.downloadClients} + {txt.settings} ); @@ -139,7 +143,7 @@ class NavBar extends Component { return ( - Translation Exchange + BTT Exchanger diff --git a/src/js/pages/Login/LoginPage.js b/src/js/pages/Login/LoginPage.js index 8987ee9b..83860b72 100644 --- a/src/js/pages/Login/LoginPage.js +++ b/src/js/pages/Login/LoginPage.js @@ -26,23 +26,35 @@ export class Welcome extends React.Component { } onSelect({key}) { - const language =key; + const language = key; this.props.updateLanguage(language); localStorage.setItem('language', language); + } + onSettingsClick() { + this.props.history.push ({ + pathname: '/settings', + }); } render() { const menu = ( this.onSelect(ky)}> - {Object.keys(Languages).map(lng => {lng} )} + {Object.keys(Languages).map(lng => + + {lng} + + )} ); return ( + + {this.props.txt.settings} settings + { return bindActionCreators( diff --git a/src/js/pages/Login/components/WelcomeComponent.js b/src/js/pages/Login/components/WelcomeComponent.js index 9b99a4b3..afcbcb01 100644 --- a/src/js/pages/Login/components/WelcomeComponent.js +++ b/src/js/pages/Login/components/WelcomeComponent.js @@ -55,7 +55,7 @@ export default class WelcomeComponent extends React.Component { -

translation Exchange

+

BTT Exchanger

diff --git a/src/js/pages/Settings/SettingsPage.js b/src/js/pages/Settings/SettingsPage.js new file mode 100644 index 00000000..ba8f69c1 --- /dev/null +++ b/src/js/pages/Settings/SettingsPage.js @@ -0,0 +1,163 @@ +import React, {Component} from 'react'; +import {connect} from 'react-redux'; +import styled from 'styled-components'; + + +export class SettingsPage extends Component { + + constructor(props) { + super(props); + this.state = { + serverName: localStorage.getItem("uploadServer") || "opentranslationtools.org" + }; + + this.onServerNameChange = this.onServerNameChange.bind(this); + } + + componentWillMount() { + } + + onServerNameChange(value) { + this.setState({ serverName: value }); + } + + onSaveClick() { + localStorage.setItem("uploadServer", this.state.serverName); + alert("Settings saved! Please restart the app."); + } + + onRestoreClick() { + this.setState({ serverName: "opentranslationtools.org" }); + } + + render() { + const {txt} = this.props; + + return ( + + + this.props.history.goBack()}> + arrow_backward {txt.goBack} + + +
+ {txt.settings} +
+ + + + {txt.serverName} + + {this.onServerNameChange(e.target.value)}} /> + + + + + + {txt.save} + {txt.restoreDefaults} + +
+ ); + } + +} + +const Container= styled.div` + height: 100%; + width: 100%; + overflow-y: hidden; + overflow-x: hidden; + display: flex; + flex-direction: column; + justify-content: center; + margin: 0 auto; + padding: 0 10vh; + position: relative; +`; +Container.displayName = 'Container'; + +const Header = styled.div` + font-size: 22px; + margin-top: 10vh; + text-align: center; + height: 5vh; +`; +Header.displayName = 'Header'; + +const BackLink = styled.a` + position: absolute; + top: 30px; + left: 30px; + cursor: pointer; + color: #4183c4 !important; + display: block; + font-size: 22px; + + i { + width: 3vh; + } +`; +BackLink.displayName = 'BackLink'; + +const SettingsContainer= styled.div` + display: flex; + flex-direction: column; + margin-top: 50px; +`; +SettingsContainer.displayName = 'SettingsContainer'; + +const SettingsItem = styled.div` + display: flex; +`; +SettingsItem.displayName = 'SettingsItem'; + +const SettingsTitle = styled.div` + width: 200px; + font-weight: bold; +`; +SettingsTitle.displayName = 'SettingsTitle'; + +const SettingsValue = styled.div` + width: 300px; +`; +SettingsValue.displayName = 'SettingsValue'; + +const SettingsInput = styled.input` + width: 300px; + height: 30px; + border-radius: 5px; + padding: 0 5px; +`; +SettingsInput.displayName = 'SettingsInput'; + +const ButtonsContainer = styled.div` + width: 100%; + margin-top: 50px; + display: flex; +`; +ButtonsContainer.displayName = 'ButtonsContainer'; + +const SaveButton = styled.a` + cursor: pointer; + display: block; + margin-right: 5vh; + font-weight: bold; +`; +SaveButton.displayName = 'SaveButton'; + +const RestoreDefaultsButton = styled.a` + cursor: pointer; + display: block; + font-weight: bold; +`; +RestoreDefaultsButton.displayName = 'RestoreDefaultsButton'; + +const mapStateToProps = state => { + const {txt} = state.geolocation; + return {txt}; +}; + +export default connect(mapStateToProps) (SettingsPage); \ No newline at end of file diff --git a/src/languages/textToDisplay.json b/src/languages/textToDisplay.json index 38646b54..904ebf7c 100644 --- a/src/languages/textToDisplay.json +++ b/src/languages/textToDisplay.json @@ -91,7 +91,10 @@ "selectAll": "Pili-a tanan", "noDownloads": "Wala nay ma-download :(", "recommended": "rekomendasyon", - "downloadClients": "I-Download ang client apps" + "downloadClients": "I-Download ang client apps", + "settings": "Setting", + "restoreDefaults": "Iuli ang mga default", + "save": "Luwasa" }, "Ilocano": { "continue": "Continue", @@ -185,7 +188,10 @@ "selectAll": "Piliem amin", "noDownloads": "Awan pulos ti nadownload :(", "recommended": "nai- rekomenda", - "downloadClients": "I- Download ti client apps" + "downloadClients": "I- Download ti client apps", + "settings": "Setting", + "restoreDefaults": "Restore defaults", + "save": "Save" }, "Tagalog": { "continue": "Magpatuloy", @@ -279,7 +285,10 @@ "selectAll": "Piliin lahat", "noDownloads": "Walang ma-download :(", "recommended": "inirerekomenda", - "downloadClients": "I-Download ang client apps" + "downloadClients": "I-Download ang client apps", + "settings": "Setting", + "restoreDefaults": "Restore defaults", + "save": "Save" }, "English": { "continue": "Continue", @@ -373,7 +382,11 @@ "selectAll": "Select All", "noDownloads": "Nothing to download :(", "recommended": "recommended", - "downloadClients": "Download client apps" + "downloadClients": "Download client apps", + "settings": "Settings", + "serverName": "Server Name", + "restoreDefaults": "Restore defaults", + "save": "Save" }, "Spanish": { "continue": "Continuar", @@ -466,6 +479,10 @@ "selectAll": "Seleccionar todo", "noDownloads": "Nada para descargar :(", "recommended": "recomendado", - "downloadClients": "Descargar aplicaciones de cliente" + "downloadClients": "Descargar aplicaciones de cliente", + "settings": "Ajustes", + "serverName": "Nombre del servidor", + "restoreDefaults": "Restaurar los valores predeterminados", + "save": "Salvar" } } diff --git a/src/views/index.ejs b/src/views/index.ejs index 3339582b..5578ada0 100644 --- a/src/views/index.ejs +++ b/src/views/index.ejs @@ -3,7 +3,7 @@ - Translation Exchange + BTT Exchanger From 851fb7860a42ee56c507b141e07027d9664d300e Mon Sep 17 00:00:00 2001 From: mxaln Date: Tue, 29 Oct 2019 16:29:56 -0400 Subject: [PATCH 3/3] Added offline version of semantic ui css lib --- package.json | 2 +- src/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 60b487e7..fa674703 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,7 @@ "redux-logger": "~3.0.6", "redux-mock-store": "~1.3.0", "redux-thunk": "~2.2.0", - "semantic-ui-css": "~2.2.11", + "semantic-ui-css-offline": "~1.0.1-2.3.2", "semantic-ui-react": "~0.71.0", "spark-md5": "~3.0.0", "styled-components": "~3.2.1", diff --git a/src/index.js b/src/index.js index eb6b0d9a..f1fdcfee 100644 --- a/src/index.js +++ b/src/index.js @@ -8,7 +8,7 @@ import ReactDOM from 'react-dom'; import { HashRouter } from 'react-router-dom'; import App from './App'; -import 'semantic-ui-css/semantic.min.css'; +import 'semantic-ui-css-offline/semantic.min.css'; import './index.css'; import { Provider } from 'react-redux'; import store from './js/store';