From 8156f6cd0786c9980da6546b3e38ad0c51e4cfbf Mon Sep 17 00:00:00 2001
From: Akshit Kr Nagpal <akshitkrnagpal@gmail.com>
Date: Wed, 25 Jul 2018 13:36:55 +0200
Subject: [PATCH] Add recent-list

---
 .../recent-list/components/RecentList.js      | 122 ++++++++++++++++++
 app/features/recent-list/components/index.js  |   1 +
 app/features/recent-list/index.js             |   4 +
 app/features/recent-list/reducer.js           |  89 +++++++++++++
 .../recent-list/styled/ConferenceCard.js      |  18 +++
 .../recent-list/styled/RecentListContainer.js |   9 ++
 .../recent-list/styled/TruncatedText.js       |   8 ++
 app/features/recent-list/styled/index.js      |   3 +
 app/features/recent-list/types.js             |  24 ++++
 app/features/redux/reducers.js                |   2 +
 app/features/redux/store.js                   |   1 +
 app/features/welcome/components/Welcome.js    |  10 +-
 app/features/welcome/styled/Body.js           |  12 ++
 .../welcome/styled/{Content.js => Header.js}  |   2 +-
 .../styled/{WelcomeWrapper.js => Wrapper.js}  |   3 +-
 app/features/welcome/styled/index.js          |   5 +-
 package-lock.json                             |   5 +
 package.json                                  |   1 +
 18 files changed, 312 insertions(+), 7 deletions(-)
 create mode 100644 app/features/recent-list/components/RecentList.js
 create mode 100644 app/features/recent-list/components/index.js
 create mode 100644 app/features/recent-list/index.js
 create mode 100644 app/features/recent-list/reducer.js
 create mode 100644 app/features/recent-list/styled/ConferenceCard.js
 create mode 100644 app/features/recent-list/styled/RecentListContainer.js
 create mode 100644 app/features/recent-list/styled/TruncatedText.js
 create mode 100644 app/features/recent-list/styled/index.js
 create mode 100644 app/features/recent-list/types.js
 create mode 100644 app/features/welcome/styled/Body.js
 rename app/features/welcome/styled/{Content.js => Header.js} (88%)
 rename app/features/welcome/styled/{WelcomeWrapper.js => Wrapper.js} (69%)

diff --git a/app/features/recent-list/components/RecentList.js b/app/features/recent-list/components/RecentList.js
new file mode 100644
index 0000000..bc6819a
--- /dev/null
+++ b/app/features/recent-list/components/RecentList.js
@@ -0,0 +1,122 @@
+// @flow
+
+import moment from 'moment';
+import React, { Component } from 'react';
+import { connect } from 'react-redux';
+import type { Dispatch } from 'redux';
+import { push } from 'react-router-redux';
+
+import { ConferenceCard, RecentListContainer, TruncatedText } from '../styled';
+import type { RecentListItem } from '../types';
+
+type Props = {
+
+    /**
+     * Redux dispatch.
+     */
+    dispatch: Dispatch<*>;
+
+    /**
+     * Array of recent conferences.
+     */
+    _recentList: Array<RecentListItem>;
+};
+
+/**
+ * Recent List Component.
+ */
+class RecentList extends Component<Props, *> {
+    /**
+     * Render function of component.
+     *
+     * @returns {ReactElement}
+     */
+    render() {
+        return (
+            <RecentListContainer>
+                {
+                    this.props._recentList.map(
+                        conference => this._renderRecentListEntry(conference)
+                    )
+                }
+            </RecentListContainer>
+        );
+    }
+
+    /**
+     * Creates a handler for navigatint to a conference.
+     *
+     * @param {RecentListItem} conference - Conference Details.
+     * @returns {void}
+     */
+    _onNavigateToConference(conference: RecentListItem) {
+        return () => this.props.dispatch(push('/conference', conference));
+    }
+
+    /**
+     * Renders the conference card.
+     *
+     * @param {RecentListItem} conference - Conference Details.
+     * @returns {ReactElement}
+     */
+    _renderRecentListEntry(conference: RecentListItem) {
+        return (
+            <ConferenceCard
+                key = { conference.startTime }
+                onClick = { this._onNavigateToConference(conference) }>
+                <TruncatedText>
+                    { conference.room }
+                </TruncatedText>
+                <TruncatedText>
+                    { this._renderServerURL(conference.serverURL) }
+                </TruncatedText>
+                <TruncatedText>
+                    { this._renderTimeAndDuration(conference) }
+                </TruncatedText>
+            </ConferenceCard>
+        );
+    }
+
+    /**
+     * Returns formatted Server URL.
+     *
+     * @param {string} serverURL - Server URL.
+     * @returns {string} - Formatted server URL.
+     */
+    _renderServerURL(serverURL: string) {
+        // Strip protocol to make it cleaner.
+        return `${serverURL.replace('https://', '')}`;
+
+    }
+
+    /**
+     * Returns Date/Time and Duration of the conference in string format.
+     *
+     * @param {RecentListItem} conference - Conference Details.
+     * @returns {string} - Date/Time and Duration.
+     */
+    _renderTimeAndDuration(conference: RecentListItem) {
+        const { startTime, endTime } = conference;
+        const start = moment(startTime);
+        const end = moment(endTime);
+        const duration = moment.duration(end.diff(start)).humanize();
+
+        return `${start.calendar()}, ${duration}`;
+    }
+}
+
+/**
+ * Maps (parts of) the redux state to the React props.
+ *
+ * @param {Object} state - The redux state.
+ * @returns {{
+ *     _recentList: Array<RecentListItem>
+ * }}
+ */
+function _mapStateToProps(state: Object) {
+    return {
+        _recentList: state.recentList.recentList
+    };
+}
+
+export default connect(_mapStateToProps)(RecentList);
diff --git a/app/features/recent-list/components/index.js b/app/features/recent-list/components/index.js
new file mode 100644
index 0000000..03545a4
--- /dev/null
+++ b/app/features/recent-list/components/index.js
@@ -0,0 +1 @@
+export { default as RecentList } from './RecentList';
diff --git a/app/features/recent-list/index.js b/app/features/recent-list/index.js
new file mode 100644
index 0000000..d04b4c3
--- /dev/null
+++ b/app/features/recent-list/index.js
@@ -0,0 +1,4 @@
+export * from './components';
+export * from './styled';
+
+export { default as reducer } from './reducer';
diff --git a/app/features/recent-list/reducer.js b/app/features/recent-list/reducer.js
new file mode 100644
index 0000000..13c15ac
--- /dev/null
+++ b/app/features/recent-list/reducer.js
@@ -0,0 +1,89 @@
+// @flow
+
+import { CONFERENCE_ENDED, CONFERENCE_JOINED } from '../conference';
+
+import type { RecentListItem } from './types';
+
+type State = {
+    recentList: Array<RecentListItem>;
+};
+
+const DEFAULT_STATE = {
+    recentList: []
+};
+
+/**
+ * Reduces redux actions for features/recent-list.
+ *
+ * @param {State} state - Current reduced redux state.
+ * @param {Object} action - Action which was dispatched.
+ * @returns {State} - Updated reduced redux state.
+ */
+export default (state: State = DEFAULT_STATE, action: Object) => {
+    switch (action.type) {
+    case CONFERENCE_ENDED:
+        return {
+            ...state,
+            recentList:
+                _updateEndtimeOfConference(state.recentList, action.conference)
+        };
+
+    case CONFERENCE_JOINED:
+        return {
+            ...state,
+            recentList: _insertConference(state.recentList, action.conference)
+        };
+
+    default:
+        return state;
+    }
+};
+
+/**
+ * Insert Conference details in the recent list array.
+ *
+ * @param {Array<RecentListItem>} recentList - Previous recent list array.
+ * @param {RecentListItem} newConference - Conference that has to be added
+ * to recent list.
+ * @returns {Array<RecentListItem>} - Updated recent list array.
+ */
+function _insertConference(
+        recentList: Array<RecentListItem>,
+        newConference: RecentListItem
+) {
+    // Add start time to conference.
+    newConference.startTime = Date.now();
+
+    // Remove same conference.
+    const newRecentList = recentList.filter(
+        (conference: RecentListItem) => conference.room !== newConference.room
+            || conference.serverURL !== newConference.serverURL);
+
+    // Add the conference at the beginning.
+    newRecentList.unshift(newConference);
+
+    return newRecentList;
+}
+
+/**
+ * Update the EndTime of the last conference.
+ *
+ * @param {Array<RecentListItem>} recentList - Previous recent list array.
+ * @param {RecentListItem} conference - Conference for which endtime has to
+ * be updated.
+ * @returns {Array<RecentListItem>} - Updated recent list array.
+ */
+function _updateEndtimeOfConference(
+        recentList: Array<RecentListItem>,
+        conference: RecentListItem
+) {
+    for (const item of recentList) {
+        if (item.room === conference.room
+                && item.serverURL === conference.serverURL) {
+            item.endTime = Date.now();
+            break;
+        }
+    }
+
+    return recentList;
+}
diff --git a/app/features/recent-list/styled/ConferenceCard.js b/app/features/recent-list/styled/ConferenceCard.js
new file mode 100644
index 0000000..fff3f75
--- /dev/null
+++ b/app/features/recent-list/styled/ConferenceCard.js
@@ -0,0 +1,18 @@
+// @flow
+
+import styled from 'styled-components';
+
+export default styled.div`
+    background: #1754A9;
+    border-radius: 0.5em;
+    color: white;
+    display: flex;
+    flex-direction: column;
+    font-size: 0.9em;
+    margin: 0.5em;
+    padding: 1em;
+    
+    &:hover {
+        cursor: pointer;
+    }
+`;
diff --git a/app/features/recent-list/styled/RecentListContainer.js b/app/features/recent-list/styled/RecentListContainer.js
new file mode 100644
index 0000000..b97ba86
--- /dev/null
+++ b/app/features/recent-list/styled/RecentListContainer.js
@@ -0,0 +1,9 @@
+// @flow
+
+import styled from 'styled-components';
+
+export default styled.div`
+    display: grid;
+    grid-template-columns: repeat(3, 33.3%);
+    padding: 0.5em;
+`;
diff --git a/app/features/recent-list/styled/TruncatedText.js b/app/features/recent-list/styled/TruncatedText.js
new file mode 100644
index 0000000..3a27ea7
--- /dev/null
+++ b/app/features/recent-list/styled/TruncatedText.js
@@ -0,0 +1,8 @@
+// @flow
+
+import styled from 'styled-components';
+
+export default styled.span`
+    overflow: hidden;
+    text-overflow: ellipsis;
+`;
diff --git a/app/features/recent-list/styled/index.js b/app/features/recent-list/styled/index.js
new file mode 100644
index 0000000..1affa12
--- /dev/null
+++ b/app/features/recent-list/styled/index.js
@@ -0,0 +1,3 @@
+export { default as ConferenceCard } from './ConferenceCard';
+export { default as RecentListContainer } from './RecentListContainer';
+export { default as TruncatedText } from './TruncatedText';
diff --git a/app/features/recent-list/types.js b/app/features/recent-list/types.js
new file mode 100644
index 0000000..1fef7fd
--- /dev/null
+++ b/app/features/recent-list/types.js
@@ -0,0 +1,24 @@
+// @flow
+
+export type RecentListItem = {
+
+    /**
+     * Timestamp of ending time of conference.
+     */
+    endTime: number;
+
+    /**
+     * Conference Room Name.
+     */
+    room: string;
+
+    /**
+     * Conference Server URL.
+     */
+    serverURL: string;
+
+    /**
+     * Timestamp of starting time of conference.
+     */
+    startTime: number;
+};
diff --git a/app/features/redux/reducers.js b/app/features/redux/reducers.js
index 667b922..2f532e6 100644
--- a/app/features/redux/reducers.js
+++ b/app/features/redux/reducers.js
@@ -3,11 +3,13 @@
 import { combineReducers } from 'redux';
 
 import { reducer as navbarReducer } from '../navbar';
+import { reducer as recentListReducer } from '../recent-list';
 import { reducer as routerReducer } from '../router';
 import { reducer as settingsReducer } from '../settings';
 
 export default combineReducers({
     navbar: navbarReducer,
+    recentList: recentListReducer,
     router: routerReducer,
     settings: settingsReducer
 });
diff --git a/app/features/redux/store.js b/app/features/redux/store.js
index 992e802..472cdbd 100644
--- a/app/features/redux/store.js
+++ b/app/features/redux/store.js
@@ -11,6 +11,7 @@ const persistConfig = {
     key: 'root',
     storage: createElectronStorage(),
     whitelist: [
+        'recentList',
         'settings'
     ]
 };
diff --git a/app/features/welcome/components/Welcome.js b/app/features/welcome/components/Welcome.js
index 648d0ef..519016d 100644
--- a/app/features/welcome/components/Welcome.js
+++ b/app/features/welcome/components/Welcome.js
@@ -11,9 +11,10 @@ import { connect } from 'react-redux';
 import { push } from 'react-router-redux';
 
 import { Navbar } from '../../navbar';
+import { RecentList } from '../../recent-list';
 import { normalizeServerURL } from '../../utils';
 
-import { WelcomeWrapper as Wrapper, Content, Form } from '../styled';
+import { Body, Form, Header, Wrapper } from '../styled';
 
 
 type Props = {
@@ -82,7 +83,7 @@ class Welcome extends Component<Props, State> {
             <Page navigation = { <Navbar /> }>
                 <AtlasKitThemeProvider mode = 'light'>
                     <Wrapper>
-                        <Content>
+                        <Header>
                             <Form onSubmit = { this._onFormSubmit }>
                                 <FieldTextStateless
                                     autoFocus = { true }
@@ -99,7 +100,10 @@ class Welcome extends Component<Props, State> {
                                 type = 'button'>
                                 GO
                             </Button>
-                        </Content>
+                        </Header>
+                        <Body>
+                            <RecentList />
+                        </Body>
                     </Wrapper>
                 </AtlasKitThemeProvider>
             </Page>
diff --git a/app/features/welcome/styled/Body.js b/app/features/welcome/styled/Body.js
new file mode 100644
index 0000000..ad74e1c
--- /dev/null
+++ b/app/features/welcome/styled/Body.js
@@ -0,0 +1,12 @@
+// @flow
+
+import styled from 'styled-components';
+
+export default styled.div`
+    margin: 0 12.5%;
+    overflow: scroll;
+
+    ::-webkit-scrollbar {
+        display: none;
+    }
+`;
diff --git a/app/features/welcome/styled/Content.js b/app/features/welcome/styled/Header.js
similarity index 88%
rename from app/features/welcome/styled/Content.js
rename to app/features/welcome/styled/Header.js
index 33fcb67..e05f5d4 100644
--- a/app/features/welcome/styled/Content.js
+++ b/app/features/welcome/styled/Header.js
@@ -6,5 +6,5 @@ export default styled.div`
     align-items: center;
     display: flex;
     margin: 0 auto;
-    padding: 30px;
+    padding: 8em;
 `;
diff --git a/app/features/welcome/styled/WelcomeWrapper.js b/app/features/welcome/styled/Wrapper.js
similarity index 69%
rename from app/features/welcome/styled/WelcomeWrapper.js
rename to app/features/welcome/styled/Wrapper.js
index 3fe50b7..5771c30 100644
--- a/app/features/welcome/styled/WelcomeWrapper.js
+++ b/app/features/welcome/styled/Wrapper.js
@@ -3,7 +3,8 @@
 import styled from 'styled-components';
 
 export default styled.div`
-    background: linear-gradient(#165ecc,#44A5FF);
+    background: #1D69D4;
     display: flex;
+    flex-direction: column;
     height: 100vh;
 `;
diff --git a/app/features/welcome/styled/index.js b/app/features/welcome/styled/index.js
index 6b132d9..191461c 100644
--- a/app/features/welcome/styled/index.js
+++ b/app/features/welcome/styled/index.js
@@ -1,3 +1,4 @@
-export { default as Content } from './Content';
+export { default as Body } from './Body';
 export { default as Form } from './Form';
-export { default as WelcomeWrapper } from './WelcomeWrapper';
+export { default as Header } from './Header';
+export { default as Wrapper } from './Wrapper';
diff --git a/package-lock.json b/package-lock.json
index 14c8a4d..d68d542 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7129,6 +7129,11 @@
         }
       }
     },
+    "moment": {
+      "version": "2.22.2",
+      "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz",
+      "integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y="
+    },
     "mousetrap": {
       "version": "1.6.2",
       "resolved": "https://registry.npmjs.org/mousetrap/-/mousetrap-1.6.2.tgz",
diff --git a/package.json b/package.json
index a5c7234..27fdc8d 100644
--- a/package.json
+++ b/package.json
@@ -93,6 +93,7 @@
     "history": "4.7.2",
     "jitsi-meet-electron-utils": "github:jitsi/jitsi-meet-electron-utils#1972c3bf0884ace68eb496894dabae593d6dbf49",
     "js-utils": "github:jitsi/js-utils#0c53500a5120be2aa3fc590f0f932a0d4771920f",
+    "moment": "2.22.2",
     "mousetrap": "1.6.2",
     "react": "16.4.1",
     "react-dom": "16.4.1",
-- 
GitLab