Przeglądaj źródła

feat(polls-history): control polls through local storage

factor2
Calin-Teodor 1 rok temu
rodzic
commit
60b4581cb5

+ 1
- 0
lang/main.json Wyświetl plik

877
             "submit": "Submit"
877
             "submit": "Submit"
878
         },
878
         },
879
         "by": "By {{ name }}",
879
         "by": "By {{ name }}",
880
+        "closeButton": "Close poll",
880
         "create": {
881
         "create": {
881
             "addOption": "Add option",
882
             "addOption": "Add option",
882
             "answerPlaceholder": "Option {{index}}",
883
             "answerPlaceholder": "Option {{index}}",

+ 1
- 0
react/features/app/middlewares.any.ts Wyświetl plik

39
 import '../overlay/middleware';
39
 import '../overlay/middleware';
40
 import '../participants-pane/middleware';
40
 import '../participants-pane/middleware';
41
 import '../polls/middleware';
41
 import '../polls/middleware';
42
+import '../polls-history/middleware';
42
 import '../reactions/middleware';
43
 import '../reactions/middleware';
43
 import '../recent-list/middleware';
44
 import '../recent-list/middleware';
44
 import '../recording/middleware';
45
 import '../recording/middleware';

+ 1
- 0
react/features/app/reducers.any.ts Wyświetl plik

41
 import '../overlay/reducer';
41
 import '../overlay/reducer';
42
 import '../participants-pane/reducer';
42
 import '../participants-pane/reducer';
43
 import '../polls/reducer';
43
 import '../polls/reducer';
44
+import '../polls-history/reducer';
44
 import '../reactions/reducer';
45
 import '../reactions/reducer';
45
 import '../recent-list/reducer';
46
 import '../recent-list/reducer';
46
 import '../recording/reducer';
47
 import '../recording/reducer';

+ 2
- 0
react/features/app/types.ts Wyświetl plik

60
 import { IOverlayState } from '../overlay/reducer';
60
 import { IOverlayState } from '../overlay/reducer';
61
 import { IParticipantsPaneState } from '../participants-pane/reducer';
61
 import { IParticipantsPaneState } from '../participants-pane/reducer';
62
 import { IPollsState } from '../polls/reducer';
62
 import { IPollsState } from '../polls/reducer';
63
+import { IPollsHistoryState } from '../polls-history/reducer';
63
 import { IPowerMonitorState } from '../power-monitor/reducer';
64
 import { IPowerMonitorState } from '../power-monitor/reducer';
64
 import { IPrejoinState } from '../prejoin/reducer';
65
 import { IPrejoinState } from '../prejoin/reducer';
65
 import { IReactionsState } from '../reactions/reducer';
66
 import { IReactionsState } from '../reactions/reducer';
149
     'features/overlay': IOverlayState;
150
     'features/overlay': IOverlayState;
150
     'features/participants-pane': IParticipantsPaneState;
151
     'features/participants-pane': IParticipantsPaneState;
151
     'features/polls': IPollsState;
152
     'features/polls': IPollsState;
153
+    'features/polls-history': IPollsHistoryState;
152
     'features/power-monitor': IPowerMonitorState;
154
     'features/power-monitor': IPowerMonitorState;
153
     'features/prejoin': IPrejoinState;
155
     'features/prejoin': IPrejoinState;
154
     'features/reactions': IReactionsState;
156
     'features/reactions': IReactionsState;

+ 0
- 8
react/features/base/conference/functions.ts Wyświetl plik

37
  */
37
  */
38
 export const getConferenceState = (state: IReduxState) => state['features/base/conference'];
38
 export const getConferenceState = (state: IReduxState) => state['features/base/conference'];
39
 
39
 
40
-/**
41
- * Is the conference joined or not.
42
- *
43
- * @param {IReduxState} state - Global state.
44
- * @returns {boolean}
45
- */
46
-export const getIsConferenceJoined = (state: IReduxState) => Boolean(getConferenceState(state).conference);
47
-
48
 /**
40
 /**
49
  * Attach a set of local tracks to a conference.
41
  * Attach a set of local tracks to a conference.
50
  *
42
  *

+ 23
- 0
react/features/polls-history/actionTypes.ts Wyświetl plik

1
+/**
2
+ * The type of the action which signals that we need to remove poll from the history(local storage).
3
+ *
4
+ * {
5
+ *     type: REMOVE_POLL_FROM_HISTORY,
6
+ *     meetingId: string,
7
+ *     pollId: string,
8
+ *     poll: IPoll
9
+ * }
10
+ */
11
+export const REMOVE_POLL_FROM_HISTORY = 'REMOVE_POLL_FROM_HISTORY';
12
+
13
+/**
14
+ * The type of the action triggered when the poll is saved in history(local storage).
15
+ *
16
+ * {
17
+ *     type: SAVE_POLL_IN_HISTORY,
18
+ *     poll: Poll,
19
+ *     pollId: string,
20
+ *     saved: boolean
21
+ * }
22
+ */
23
+export const SAVE_POLL_IN_HISTORY = 'SAVE_POLL_IN_HISTORY';

+ 47
- 0
react/features/polls-history/actions.ts Wyświetl plik

1
+import { IPoll } from '../polls/types';
2
+
3
+import { REMOVE_POLL_FROM_HISTORY, SAVE_POLL_IN_HISTORY } from './actionTypes';
4
+
5
+/**
6
+ * Action to signal saving a poll in history(local storage).
7
+ *
8
+ * @param {string} meetingId - The id of the meeting in which polls get to be saved.
9
+ * @param {string} pollId - The id of the poll that gets to be saved.
10
+ * @param {IPoll} poll - The Poll object that gets to be saved.
11
+ * @returns {{
12
+ *     type: SAVE_POLL_IN_HISTORY,
13
+ *     meetingId: string,
14
+ *     pollId: string,
15
+ *     poll: IPoll
16
+ * }}
17
+ */
18
+export function savePollInHistory(meetingId: string | undefined, pollId: string, poll: IPoll) {
19
+    return {
20
+        type: SAVE_POLL_IN_HISTORY,
21
+        meetingId,
22
+        pollId,
23
+        poll
24
+    };
25
+}
26
+
27
+/**
28
+ * Action to signal that existing poll needs to be deleted from history(local storage).
29
+ *
30
+ * @param {string} meetingId - The id of the meeting in which poll gets to be removed.
31
+ * @param {string} pollId - The id of the poll that gets to be removed.
32
+ * @param {IPoll} poll - The incoming IPoll object.
33
+ * @returns {{
34
+ *     type: REMOVE_POLL_FROM_HISTORY,
35
+ *     meetingId: string,
36
+ *     pollId: string,
37
+ *     poll: IPoll
38
+ * }}
39
+ */
40
+export const removePollFromHistory = (meetingId: string | undefined, pollId: string, poll: IPoll) => {
41
+    return {
42
+        type: REMOVE_POLL_FROM_HISTORY,
43
+        meetingId,
44
+        pollId,
45
+        poll
46
+    };
47
+};

+ 46
- 0
react/features/polls-history/middleware.ts Wyświetl plik

1
+import { CONFERENCE_JOINED } from '../base/conference/actionTypes';
2
+import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
3
+import { REMOVE_POLL, SAVE_POLL } from '../polls/actionTypes';
4
+import { savePoll } from '../polls/actions';
5
+
6
+import { removePollFromHistory, savePollInHistory } from './actions';
7
+
8
+MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
9
+    const result = next(action);
10
+    const { room: meetingId } = getState()['features/base/conference'];
11
+
12
+    switch (action.type) {
13
+
14
+    case CONFERENCE_JOINED: {
15
+        const state = getState();
16
+        const pollsHistory = meetingId && state['features/polls-history'].polls?.[meetingId];
17
+
18
+        if (!pollsHistory) {
19
+            return null;
20
+        }
21
+
22
+        for (const key in pollsHistory) {
23
+            if (pollsHistory.hasOwnProperty(key) && pollsHistory[key].saved) {
24
+                dispatch(savePoll(key, pollsHistory[key]));
25
+            }
26
+        }
27
+        break;
28
+    }
29
+
30
+    case REMOVE_POLL: {
31
+        const { poll, pollId } = action;
32
+
33
+        dispatch(removePollFromHistory(meetingId, pollId, poll));
34
+        break;
35
+    }
36
+
37
+    case SAVE_POLL: {
38
+        const { poll, pollId } = action;
39
+
40
+        dispatch(savePollInHistory(meetingId, pollId, poll));
41
+        break;
42
+    }
43
+    }
44
+
45
+    return result;
46
+});

+ 52
- 0
react/features/polls-history/reducer.ts Wyświetl plik

1
+import PersistenceRegistry from '../base/redux/PersistenceRegistry';
2
+import ReducerRegistry from '../base/redux/ReducerRegistry';
3
+import { IPoll } from '../polls/types';
4
+
5
+import { REMOVE_POLL_FROM_HISTORY, SAVE_POLL_IN_HISTORY } from './actionTypes';
6
+
7
+const INITIAL_STATE = {
8
+    polls: {}
9
+};
10
+
11
+export interface IPollsHistoryState {
12
+    polls: {
13
+        [meetingId: string]: {
14
+            [pollId: string]: IPoll;
15
+        };
16
+    };
17
+}
18
+
19
+const STORE_NAME = 'features/polls-history';
20
+
21
+PersistenceRegistry.register(STORE_NAME, INITIAL_STATE);
22
+
23
+ReducerRegistry.register<IPollsHistoryState>(STORE_NAME, (state = INITIAL_STATE, action): IPollsHistoryState => {
24
+    switch (action.type) {
25
+
26
+    case REMOVE_POLL_FROM_HISTORY: {
27
+        if (Object.keys(state.polls[action.meetingId] ?? {})?.length === 1) {
28
+            delete state.polls[action.meetingId];
29
+        } else {
30
+            delete state.polls[action.meetingId]?.[action.pollId];
31
+        }
32
+
33
+        return state;
34
+    }
35
+
36
+    case SAVE_POLL_IN_HISTORY: {
37
+        return {
38
+            ...state,
39
+            polls: {
40
+                ...state.polls,
41
+                [action.meetingId]: {
42
+                    ...state.polls[action.meetingId],
43
+                    [action.pollId]: action.poll
44
+                }
45
+            }
46
+        };
47
+    }
48
+
49
+    default:
50
+        return state;
51
+    }
52
+});

+ 5
- 4
react/features/polls/actionTypes.ts Wyświetl plik

10
 
10
 
11
 /**
11
 /**
12
  * The type of the action which signals that we need to clear all polls from the state.
12
  * The type of the action which signals that we need to clear all polls from the state.
13
- * For example we are moving to another conference.
13
+ * For example, we are moving to another conference.
14
  *
14
  *
15
  * {
15
  * {
16
  *     type: CLEAR_POLLS
16
  *     type: CLEAR_POLLS
65
 export const REGISTER_VOTE = 'REGISTER_VOTE';
65
 export const REGISTER_VOTE = 'REGISTER_VOTE';
66
 
66
 
67
 /**
67
 /**
68
- * The type of the action which retracts a vote.
68
+ * The type of the action which signals that we need to remove poll.
69
  *
69
  *
70
  * {
70
  * {
71
- *     type: RETRACT_VOTE,
71
+ *     type: REMOVE_POLL,
72
  *     pollId: string,
72
  *     pollId: string,
73
+ *     poll: IPoll
73
  * }
74
  * }
74
  */
75
  */
75
-export const RETRACT_VOTE = 'RETRACT_VOTE';
76
+export const REMOVE_POLL = 'REMOVE_POLL';
76
 
77
 
77
 /**
78
 /**
78
  * The type of the action triggered when the poll tab in chat pane is closed
79
  * The type of the action triggered when the poll tab in chat pane is closed

+ 41
- 40
react/features/polls/actions.ts Wyświetl plik

5
     RECEIVE_ANSWER,
5
     RECEIVE_ANSWER,
6
     RECEIVE_POLL,
6
     RECEIVE_POLL,
7
     REGISTER_VOTE,
7
     REGISTER_VOTE,
8
+    REMOVE_POLL,
8
     RESET_NB_UNREAD_POLLS,
9
     RESET_NB_UNREAD_POLLS,
9
-    RETRACT_VOTE,
10
     SAVE_POLL
10
     SAVE_POLL
11
 } from './actionTypes';
11
 } from './actionTypes';
12
 import { IAnswer, IPoll } from './types';
12
 import { IAnswer, IPoll } from './types';
19
  * }}
19
  * }}
20
  */
20
  */
21
 export const clearPolls = () => {
21
 export const clearPolls = () => {
22
-    return { type: CLEAR_POLLS };
22
+    return {
23
+        type: CLEAR_POLLS
24
+    };
23
 };
25
 };
24
 
26
 
25
 /**
27
 /**
50
  * @param {boolean} notify - Whether to send or not a notification.
52
  * @param {boolean} notify - Whether to send or not a notification.
51
  * @returns {{
53
  * @returns {{
52
  *     type: RECEIVE_POLL,
54
  *     type: RECEIVE_POLL,
53
- *     poll: IPoll,
54
  *     pollId: string,
55
  *     pollId: string,
56
+ *     poll: IPoll,
55
  *     notify: boolean
57
  *     notify: boolean
56
  * }}
58
  * }}
57
  */
59
  */
58
 export const receivePoll = (pollId: string, poll: IPoll, notify: boolean) => {
60
 export const receivePoll = (pollId: string, poll: IPoll, notify: boolean) => {
59
     return {
61
     return {
60
         type: RECEIVE_POLL,
62
         type: RECEIVE_POLL,
61
-        poll,
62
         pollId,
63
         pollId,
64
+        poll,
63
         notify
65
         notify
64
     };
66
     };
65
 };
67
 };
71
  * @param {IAnswer} answer - The incoming Answer object.
73
  * @param {IAnswer} answer - The incoming Answer object.
72
  * @returns {{
74
  * @returns {{
73
  *     type: RECEIVE_ANSWER,
75
  *     type: RECEIVE_ANSWER,
74
- *     answer: IAnswer,
75
- *     pollId: string
76
+ *     pollId: string,
77
+ *     answer: IAnswer
76
  * }}
78
  * }}
77
  */
79
  */
78
 export const receiveAnswer = (pollId: string, answer: IAnswer) => {
80
 export const receiveAnswer = (pollId: string, answer: IAnswer) => {
79
     return {
81
     return {
80
         type: RECEIVE_ANSWER,
82
         type: RECEIVE_ANSWER,
81
-        answer,
82
-        pollId
83
+        pollId,
84
+        answer
83
     };
85
     };
84
 };
86
 };
85
 
87
 
90
  * @param {?Array<boolean>} answers - The new answers.
92
  * @param {?Array<boolean>} answers - The new answers.
91
  * @returns {{
93
  * @returns {{
92
  *     type: REGISTER_VOTE,
94
  *     type: REGISTER_VOTE,
93
- *     answers: ?Array<boolean>,
94
- *     pollId: string
95
+ *     pollId: string,
96
+ *     answers: ?Array<boolean>
95
  * }}
97
  * }}
96
  */
98
  */
97
 export const registerVote = (pollId: string, answers: Array<boolean> | null) => {
99
 export const registerVote = (pollId: string, answers: Array<boolean> | null) => {
98
     return {
100
     return {
99
         type: REGISTER_VOTE,
101
         type: REGISTER_VOTE,
100
-        answers,
101
-        pollId
102
-    };
103
-};
104
-
105
-/**
106
- * Action to retract a vote on a poll.
107
- *
108
- * @param {string} pollId - The id of the poll.
109
- * @returns {{
110
- *     type: RETRACT_VOTE,
111
- *     pollId: string
112
- * }}
113
- */
114
-export const retractVote = (pollId: string) => {
115
-    return {
116
-        type: RETRACT_VOTE,
117
-        pollId
102
+        pollId,
103
+        answers
118
     };
104
     };
119
 };
105
 };
120
 
106
 
121
 /**
107
 /**
122
- * Action to signal the closing of the polls tab.
108
+ * Action to signal the number reset of unread polls.
123
  *
109
  *
124
  * @returns {{
110
  * @returns {{
125
- *     type: POLL_TAB_CLOSED
111
+ *     type: RESET_NB_UNREAD_POLLS
126
  * }}
112
  * }}
127
  */
113
  */
128
 export function resetNbUnreadPollsMessages() {
114
 export function resetNbUnreadPollsMessages() {
136
  *
122
  *
137
  * @param {string} pollId - The id of the poll that gets to be saved.
123
  * @param {string} pollId - The id of the poll that gets to be saved.
138
  * @param {IPoll} poll - The Poll object that gets to be saved.
124
  * @param {IPoll} poll - The Poll object that gets to be saved.
139
- * @param {boolean} saved - Whether the poll is saved or not.
140
  * @returns {{
125
  * @returns {{
141
- *     type: RECEIVE_POLL,
142
- *     poll: IPoll,
126
+ *     type: SAVE_POLL,
127
+ *     meetingId: string,
143
  *     pollId: string,
128
  *     pollId: string,
144
- *     saved: boolean
129
+ *     poll: IPoll
145
  * }}
130
  * }}
146
  */
131
  */
147
-export function savePoll(pollId: string, poll: IPoll, saved: boolean) {
132
+export function savePoll(pollId: string, poll: IPoll) {
148
     return {
133
     return {
149
         type: SAVE_POLL,
134
         type: SAVE_POLL,
150
         pollId,
135
         pollId,
151
-        poll: {
152
-            ...poll,
153
-            saved
154
-        }
136
+        poll
155
     };
137
     };
156
 }
138
 }
157
 
139
 
161
  * @param {string} pollId - The id of the poll that gets to be edited.
143
  * @param {string} pollId - The id of the poll that gets to be edited.
162
  * @param {boolean} editing - Whether the poll is in edit mode or not.
144
  * @param {boolean} editing - Whether the poll is in edit mode or not.
163
  * @returns {{
145
  * @returns {{
164
- *     type: RECEIVE_POLL,
146
+ *     type: EDIT_POLL,
165
  *     pollId: string,
147
  *     pollId: string,
166
  *     editing: boolean
148
  *     editing: boolean
167
  * }}
149
  * }}
173
         editing
155
         editing
174
     };
156
     };
175
 }
157
 }
158
+
159
+/**
160
+ * Action to signal that existing polls needs to be removed.
161
+ *
162
+ * @param {string} pollId - The id of the poll that gets to be removed.
163
+ * @param {IPoll} poll - The incoming Poll object.
164
+ * @returns {{
165
+ *     type: REMOVE_POLL,
166
+ *     pollId: string,
167
+ *     poll: IPoll
168
+ * }}
169
+ */
170
+export const removePoll = (pollId: string, poll: IPoll) => {
171
+    return {
172
+        type: REMOVE_POLL,
173
+        pollId,
174
+        poll
175
+    };
176
+};

+ 6
- 6
react/features/polls/components/AbstractPollAnswer.tsx Wyświetl plik

7
 import { IReduxState } from '../../app/types';
7
 import { IReduxState } from '../../app/types';
8
 import { getParticipantDisplayName } from '../../base/participants/functions';
8
 import { getParticipantDisplayName } from '../../base/participants/functions';
9
 import { useBoundSelector } from '../../base/util/hooks';
9
 import { useBoundSelector } from '../../base/util/hooks';
10
-import { editPoll, registerVote, setVoteChanging } from '../actions';
10
+import { registerVote, removePoll, setVoteChanging } from '../actions';
11
 import { COMMAND_ANSWER_POLL, COMMAND_NEW_POLL } from '../constants';
11
 import { COMMAND_ANSWER_POLL, COMMAND_NEW_POLL } from '../constants';
12
+import { getPoll } from '../functions';
12
 import { IPoll } from '../types';
13
 import { IPoll } from '../types';
13
 
14
 
14
 /**
15
 /**
48
 
49
 
49
     const { pollId, setCreateMode } = props;
50
     const { pollId, setCreateMode } = props;
50
 
51
 
51
-    const conference: any = useSelector((state: IReduxState) => state['features/base/conference'].conference);
52
+    const { conference } = useSelector((state: IReduxState) => state['features/base/conference']);
52
 
53
 
53
-    const poll: IPoll = useSelector((state: IReduxState) => state['features/polls'].polls[pollId]);
54
+    const poll: IPoll = useSelector(getPoll(pollId));
54
 
55
 
55
     const { answers, lastVote, question, senderId } = poll;
56
     const { answers, lastVote, question, senderId } = poll;
56
 
57
 
75
     const dispatch = useDispatch();
76
     const dispatch = useDispatch();
76
 
77
 
77
     const submitAnswer = useCallback(() => {
78
     const submitAnswer = useCallback(() => {
78
-        conference.sendMessage({
79
+        conference?.sendMessage({
79
             type: COMMAND_ANSWER_POLL,
80
             type: COMMAND_ANSWER_POLL,
80
             pollId,
81
             pollId,
81
             answers: checkBoxStates
82
             answers: checkBoxStates
95
             answers: answers.map(answer => answer.name)
96
             answers: answers.map(answer => answer.name)
96
         });
97
         });
97
 
98
 
98
-        dispatch(editPoll(pollId, false));
99
-
99
+        dispatch(removePoll(pollId, poll));
100
     }, [ conference, question, answers ]);
100
     }, [ conference, question, answers ]);
101
 
101
 
102
     const skipAnswer = useCallback(() => {
102
     const skipAnswer = useCallback(() => {

+ 4
- 4
react/features/polls/components/AbstractPollCreate.tsx Wyświetl plik

121
         setAnswers(newAnswers);
121
         setAnswers(newAnswers);
122
     }, [ answers ]);
122
     }, [ answers ]);
123
 
123
 
124
-    const conference = useSelector((state: IReduxState) => state['features/base/conference'].conference);
124
+    const { conference } = useSelector((state: IReduxState) => state['features/base/conference']);
125
 
125
 
126
     const dispatch = useDispatch();
126
     const dispatch = useDispatch();
127
 
127
 
147
             lastVote: null,
147
             lastVote: null,
148
             question,
148
             question,
149
             answers: filteredAnswers,
149
             answers: filteredAnswers,
150
-            saved: false,
150
+            saved: true,
151
             editing: false
151
             editing: false
152
         };
152
         };
153
 
153
 
154
         if (editingPoll) {
154
         if (editingPoll) {
155
-            dispatch(savePoll(editingPoll[0], poll, true));
155
+            dispatch(savePoll(editingPoll[0], poll));
156
         } else {
156
         } else {
157
-            dispatch(savePoll(pollId, poll, true));
157
+            dispatch(savePoll(pollId, poll));
158
         }
158
         }
159
 
159
 
160
         sendAnalytics(createPollEvent('created'));
160
         sendAnalytics(createPollEvent('created'));

+ 8
- 7
react/features/polls/components/AbstractPollResults.tsx Wyświetl plik

10
 import { useBoundSelector } from '../../base/util/hooks';
10
 import { useBoundSelector } from '../../base/util/hooks';
11
 import { setVoteChanging } from '../actions';
11
 import { setVoteChanging } from '../actions';
12
 import { getPoll } from '../functions';
12
 import { getPoll } from '../functions';
13
+import { IPoll } from '../types';
13
 
14
 
14
 /**
15
 /**
15
  * The type of the React {@code Component} props of inheriting component.
16
  * The type of the React {@code Component} props of inheriting component.
53
 const AbstractPollResults = (Component: ComponentType<AbstractProps>) => (props: InputProps) => {
54
 const AbstractPollResults = (Component: ComponentType<AbstractProps>) => (props: InputProps) => {
54
     const { pollId } = props;
55
     const { pollId } = props;
55
 
56
 
56
-    const pollDetails = useSelector(getPoll(pollId));
57
-    const participant = useBoundSelector(getParticipantById, pollDetails.senderId);
57
+    const poll: IPoll = useSelector(getPoll(pollId));
58
+    const participant = useBoundSelector(getParticipantById, poll.senderId);
58
     const reduxState = useSelector((state: IReduxState) => state);
59
     const reduxState = useSelector((state: IReduxState) => state);
59
 
60
 
60
     const [ showDetails, setShowDetails ] = useState(false);
61
     const [ showDetails, setShowDetails ] = useState(false);
67
         const allVoters = new Set();
68
         const allVoters = new Set();
68
 
69
 
69
         // Getting every voters ID that participates to the poll
70
         // Getting every voters ID that participates to the poll
70
-        for (const answer of pollDetails.answers) {
71
+        for (const answer of poll.answers) {
71
             // checking if the voters is an array for supporting old structure model
72
             // checking if the voters is an array for supporting old structure model
72
             const voters: string[] = answer.voters.length ? answer.voters : Object.keys(answer.voters);
73
             const voters: string[] = answer.voters.length ? answer.voters : Object.keys(answer.voters);
73
 
74
 
74
             voters.forEach((voter: string) => allVoters.add(voter));
75
             voters.forEach((voter: string) => allVoters.add(voter));
75
         }
76
         }
76
 
77
 
77
-        return pollDetails.answers.map(answer => {
78
+        return poll.answers.map(answer => {
78
             const nrOfVotersPerAnswer = answer.voters ? Object.keys(answer.voters).length : 0;
79
             const nrOfVotersPerAnswer = answer.voters ? Object.keys(answer.voters).length : 0;
79
             const percentage = allVoters.size > 0 ? Math.round(nrOfVotersPerAnswer / allVoters.size * 100) : 0;
80
             const percentage = allVoters.size > 0 ? Math.round(nrOfVotersPerAnswer / allVoters.size * 100) : 0;
80
 
81
 
98
                 voterCount: nrOfVotersPerAnswer
99
                 voterCount: nrOfVotersPerAnswer
99
             };
100
             };
100
         });
101
         });
101
-    }, [ pollDetails.answers, showDetails ]);
102
+    }, [ poll.answers, showDetails ]);
102
 
103
 
103
     const dispatch = useDispatch();
104
     const dispatch = useDispatch();
104
     const changeVote = useCallback(() => {
105
     const changeVote = useCallback(() => {
113
             answers = { answers }
114
             answers = { answers }
114
             changeVote = { changeVote }
115
             changeVote = { changeVote }
115
             creatorName = { participant ? participant.name : '' }
116
             creatorName = { participant ? participant.name : '' }
116
-            haveVoted = { pollDetails.lastVote !== null }
117
-            question = { pollDetails.question }
117
+            haveVoted = { poll.lastVote !== null }
118
+            question = { poll.question }
118
             showDetails = { showDetails }
119
             showDetails = { showDetails }
119
             t = { t }
120
             t = { t }
120
             toggleIsDetailed = { toggleIsDetailed } />
121
             toggleIsDetailed = { toggleIsDetailed } />

+ 17
- 6
react/features/polls/components/native/PollAnswer.tsx Wyświetl plik

4
 import { Text, TextStyle, View, ViewStyle } from 'react-native';
4
 import { Text, TextStyle, View, ViewStyle } from 'react-native';
5
 import { useDispatch, useSelector } from 'react-redux';
5
 import { useDispatch, useSelector } from 'react-redux';
6
 
6
 
7
+import { IconCloseLarge } from '../../../base/icons/svg';
7
 import { getLocalParticipant } from '../../../base/participants/functions';
8
 import { getLocalParticipant } from '../../../base/participants/functions';
8
 import Button from '../../../base/ui/components/native/Button';
9
 import Button from '../../../base/ui/components/native/Button';
10
+import IconButton from '../../../base/ui/components/native/IconButton';
9
 import Switch from '../../../base/ui/components/native/Switch';
11
 import Switch from '../../../base/ui/components/native/Switch';
10
 import { BUTTON_TYPES } from '../../../base/ui/constants.native';
12
 import { BUTTON_TYPES } from '../../../base/ui/constants.native';
11
-import { editPoll } from '../../actions';
13
+import { editPoll, removePoll } from '../../actions';
12
 import { isSubmitAnswerDisabled } from '../../functions';
14
 import { isSubmitAnswerDisabled } from '../../functions';
13
 import AbstractPollAnswer, { AbstractProps } from '../AbstractPollAnswer';
15
 import AbstractPollAnswer, { AbstractProps } from '../AbstractPollAnswer';
14
 
16
 
34
 
36
 
35
     return (
37
     return (
36
         <>
38
         <>
37
-            <Text style = { dialogStyles.questionText as TextStyle } >{ poll.question }</Text>
38
-            <Text style = { dialogStyles.questionOwnerText as TextStyle } >{
39
-                t('polls.by', { name: localParticipant?.name })
40
-            }
41
-            </Text>
39
+            <View style = { dialogStyles.headerContainer as ViewStyle }>
40
+                <View>
41
+                    <Text style = { dialogStyles.questionText as TextStyle } >{ poll.question }</Text>
42
+                    <Text style = { dialogStyles.questionOwnerText as TextStyle } >{
43
+                        t('polls.by', { name: localParticipant?.name })
44
+                    }
45
+                    </Text>
46
+                </View>
47
+                {
48
+                    pollSaved && <IconButton
49
+                        onPress = { () => dispatch(removePoll(pollId, poll)) }
50
+                        src = { IconCloseLarge } />
51
+                }
52
+            </View>
42
             <View style = { pollsStyles.answerContent as ViewStyle }>
53
             <View style = { pollsStyles.answerContent as ViewStyle }>
43
                 {
54
                 {
44
                     poll.answers.map((answer, index: number) => (
55
                     poll.answers.map((answer, index: number) => (

+ 6
- 0
react/features/polls/components/native/styles.ts Wyświetl plik

4
 
4
 
5
 export const dialogStyles = createStyleSheet({
5
 export const dialogStyles = createStyleSheet({
6
 
6
 
7
+    headerContainer: {
8
+        display: 'flex',
9
+        flexDirection: 'row',
10
+        justifyContent: 'space-between'
11
+    },
12
+
7
     customContainer: {
13
     customContainer: {
8
         marginBottom: BaseTheme.spacing[3],
14
         marginBottom: BaseTheme.spacing[3],
9
         marginHorizontal: BaseTheme.spacing[3],
15
         marginHorizontal: BaseTheme.spacing[3],

+ 16
- 1
react/features/polls/components/web/PollAnswer.tsx Wyświetl plik

4
 import { useDispatch } from 'react-redux';
4
 import { useDispatch } from 'react-redux';
5
 import { makeStyles } from 'tss-react/mui';
5
 import { makeStyles } from 'tss-react/mui';
6
 
6
 
7
+import Icon from '../../../base/icons/components/Icon';
8
+import { IconCloseLarge } from '../../../base/icons/svg';
7
 import { withPixelLineHeight } from '../../../base/styles/functions.web';
9
 import { withPixelLineHeight } from '../../../base/styles/functions.web';
8
 import Button from '../../../base/ui/components/web/Button';
10
 import Button from '../../../base/ui/components/web/Button';
9
 import Checkbox from '../../../base/ui/components/web/Checkbox';
11
 import Checkbox from '../../../base/ui/components/web/Checkbox';
10
 import { BUTTON_TYPES } from '../../../base/ui/constants.web';
12
 import { BUTTON_TYPES } from '../../../base/ui/constants.web';
11
-import { editPoll } from '../../actions';
13
+import { editPoll, removePoll } from '../../actions';
12
 import { isSubmitAnswerDisabled } from '../../functions';
14
 import { isSubmitAnswerDisabled } from '../../functions';
13
 import AbstractPollAnswer, { AbstractProps } from '../AbstractPollAnswer';
15
 import AbstractPollAnswer, { AbstractProps } from '../AbstractPollAnswer';
14
 
16
 
21
             borderRadius: '8px',
23
             borderRadius: '8px',
22
             wordBreak: 'break-word'
24
             wordBreak: 'break-word'
23
         },
25
         },
26
+        closeBtn: {
27
+            cursor: 'pointer',
28
+            float: 'right'
29
+        },
24
         header: {
30
         header: {
25
             marginBottom: '24px'
31
             marginBottom: '24px'
26
         },
32
         },
73
 
79
 
74
     return (
80
     return (
75
         <div className = { classes.container }>
81
         <div className = { classes.container }>
82
+            {
83
+                pollSaved && <Icon
84
+                    ariaLabel = { t('polls.closeButton') }
85
+                    className = { classes.closeBtn }
86
+                    onClick = { () => dispatch(removePoll(pollId, poll)) }
87
+                    role = 'button'
88
+                    src = { IconCloseLarge }
89
+                    tabIndex = { 0 } />
90
+            }
76
             <div className = { classes.header }>
91
             <div className = { classes.header }>
77
                 <div className = { classes.question }>
92
                 <div className = { classes.question }>
78
                     { poll.question }
93
                     { poll.question }

+ 1
- 1
react/features/polls/components/web/PollsList.tsx Wyświetl plik

46
 const PollsList = ({ setCreateMode }: IPollListProps) => {
46
 const PollsList = ({ setCreateMode }: IPollListProps) => {
47
     const { t } = useTranslation();
47
     const { t } = useTranslation();
48
     const { classes, theme } = useStyles();
48
     const { classes, theme } = useStyles();
49
+    const { polls } = useSelector((state: IReduxState) => state['features/polls']);
49
 
50
 
50
-    const polls = useSelector((state: IReduxState) => state['features/polls'].polls);
51
     const pollListEndRef = useRef<HTMLDivElement>(null);
51
     const pollListEndRef = useRef<HTMLDivElement>(null);
52
 
52
 
53
     const scrollToBottom = useCallback(() => {
53
     const scrollToBottom = useCallback(() => {

+ 2
- 4
react/features/polls/middleware.ts Wyświetl plik

25
  */
25
  */
26
 StateListenerRegistry.register(
26
 StateListenerRegistry.register(
27
     state => getCurrentConference(state),
27
     state => getCurrentConference(state),
28
-    (conference, { dispatch }, previousConference) => {
28
+    (conference, { dispatch }, previousConference): void => {
29
         if (conference !== previousConference) {
29
         if (conference !== previousConference) {
30
-            // conference changed, left or failed...
31
-            // clean old polls
32
             dispatch(clearPolls());
30
             dispatch(clearPolls());
33
         }
31
         }
34
     });
32
     });
101
         }
99
         }
102
         break;
100
         break;
103
     }
101
     }
104
-
105
     }
102
     }
106
 
103
 
107
     return result;
104
     return result;
122
     }
119
     }
123
 
120
 
124
     switch (data.type) {
121
     switch (data.type) {
122
+
125
     case COMMAND_NEW_POLL: {
123
     case COMMAND_NEW_POLL: {
126
         const { pollId, answers, senderId, question } = data;
124
         const { pollId, answers, senderId, question } = data;
127
 
125
 

+ 34
- 19
react/features/polls/reducer.ts Wyświetl plik

7
     RECEIVE_ANSWER,
7
     RECEIVE_ANSWER,
8
     RECEIVE_POLL,
8
     RECEIVE_POLL,
9
     REGISTER_VOTE,
9
     REGISTER_VOTE,
10
+    REMOVE_POLL,
10
     RESET_NB_UNREAD_POLLS,
11
     RESET_NB_UNREAD_POLLS,
11
-    RETRACT_VOTE,
12
     SAVE_POLL
12
     SAVE_POLL
13
 } from './actionTypes';
13
 } from './actionTypes';
14
 import { IAnswer, IPoll } from './types';
14
 import { IAnswer, IPoll } from './types';
27
     };
27
     };
28
 }
28
 }
29
 
29
 
30
-ReducerRegistry.register<IPollsState>('features/polls', (state = INITIAL_STATE, action): IPollsState => {
30
+const STORE_NAME = 'features/polls';
31
+
32
+ReducerRegistry.register<IPollsState>(STORE_NAME, (state = INITIAL_STATE, action): IPollsState => {
31
     switch (action.type) {
33
     switch (action.type) {
32
 
34
 
33
     case CHANGE_VOTE: {
35
     case CHANGE_VOTE: {
54
     }
56
     }
55
 
57
 
56
     // Reducer triggered when a poll is received or saved.
58
     // Reducer triggered when a poll is received or saved.
57
-    case RECEIVE_POLL:
58
-    case SAVE_POLL: {
59
+    case RECEIVE_POLL: {
59
         return {
60
         return {
60
             ...state,
61
             ...state,
61
             polls: {
62
             polls: {
66
         };
67
         };
67
     }
68
     }
68
 
69
 
70
+    case SAVE_POLL: {
71
+        return {
72
+            ...state,
73
+            polls: {
74
+                ...state.polls,
75
+                [action.pollId]: action.poll
76
+            }
77
+        };
78
+    }
79
+
69
     // Reducer triggered when an answer is received
80
     // Reducer triggered when an answer is received
70
     // The answer is added  to an existing poll
81
     // The answer is added  to an existing poll
71
     case RECEIVE_ANSWER: {
82
     case RECEIVE_ANSWER: {
139
         };
150
         };
140
     }
151
     }
141
 
152
 
142
-    case RETRACT_VOTE: {
143
-        const { pollId }: { pollId: string; } = action;
144
-
145
-        return {
146
-            ...state,
147
-            polls: {
148
-                ...state.polls,
149
-                [pollId]: {
150
-                    ...state.polls[pollId],
151
-                    showResults: false
152
-                }
153
-            }
154
-        };
155
-    }
156
-
157
     case RESET_NB_UNREAD_POLLS: {
153
     case RESET_NB_UNREAD_POLLS: {
158
         return {
154
         return {
159
             ...state,
155
             ...state,
174
         };
170
         };
175
     }
171
     }
176
 
172
 
173
+    case REMOVE_POLL: {
174
+        if (Object.keys(state.polls ?? {})?.length === 1) {
175
+            return {
176
+                ...state,
177
+                ...INITIAL_STATE
178
+            };
179
+        }
180
+
181
+        // eslint-disable-next-line @typescript-eslint/no-unused-vars
182
+        const { [action.pollId]: _removedPoll, ...newState } = state.polls;
183
+
184
+        return {
185
+            ...state,
186
+            polls: {
187
+                ...newState
188
+            }
189
+        };
190
+    }
191
+
177
     default:
192
     default:
178
         return state;
193
         return state;
179
     }
194
     }

Ładowanie…
Anuluj
Zapisz