소스 검색

feat: convert invite dialog to react and redux

Converting the invite modal includes the following:
- Creating new react components to display InviteDialog. The
  main parent components are ShareLink and PasswordOverview,
  the later handles displaying lock state and password editing.
  These components do not make use of atlaskit as the component
  for input does not yet support readonly, so for consistency
  within the modal content no atlaskit was used.
- Using redux for keeping and accessing lock state instead of
  RoomLocker.
- Publicly exposing the redux action lockStateChanged for direct
  calling on lock events experienced on the web client.
- Removing Invite, InviteDialogView, and RoomLocker and references
  to them.
- Handling errors that occur when setting a password to preserve
  existing web funtionality.
master
Leonard Kim 8 년 전
부모
커밋
44b81b20e3
30개의 변경된 파일904개의 추가작업 그리고 728개의 파일을 삭제
  1. 19
    15
      conference.js
  2. 2
    0
      css/_variables.scss
  3. 6
    0
      css/modals/_dialog.scss
  4. 41
    1
      css/modals/invite/_invite.scss
  5. 9
    4
      lang/main.json
  6. 0
    187
      modules/UI/invite/Invite.js
  7. 0
    378
      modules/UI/invite/InviteDialogView.js
  8. 0
    103
      modules/UI/invite/RoomLocker.js
  9. 4
    4
      modules/UI/side_pannels/contactlist/ContactList.js
  10. 16
    14
      modules/UI/side_pannels/contactlist/ContactListView.js
  11. 11
    0
      react/features/base/conference/actionTypes.js
  12. 9
    3
      react/features/base/conference/actions.js
  13. 25
    4
      react/features/base/conference/reducer.js
  14. 18
    0
      react/features/invite/actions.js
  15. 137
    0
      react/features/invite/components/AddPasswordForm.js
  16. 104
    0
      react/features/invite/components/InviteDialog.js
  17. 48
    0
      react/features/invite/components/LockStatePanel.js
  18. 147
    0
      react/features/invite/components/PasswordContainer.js
  19. 117
    0
      react/features/invite/components/RemovePasswordForm.js
  20. 110
    0
      react/features/invite/components/ShareLinkForm.js
  21. 1
    0
      react/features/invite/components/index.js
  22. 2
    0
      react/features/invite/index.js
  23. 5
    3
      react/features/room-lock/components/PasswordRequiredPrompt.web.js
  24. 12
    0
      react/features/room-lock/constants.js
  25. 1
    0
      react/features/room-lock/index.js
  26. 56
    5
      react/features/room-lock/middleware.js
  27. 1
    1
      react/features/toolbox/components/Toolbox.native.js
  28. 0
    1
      react/features/toolbox/components/Toolbox.web.js
  29. 3
    1
      react/features/toolbox/defaultToolbarButtons.js
  30. 0
    4
      service/UI/UIEvents.js

+ 19
- 15
conference.js 파일 보기

@@ -2,7 +2,6 @@
2 2
 const logger = require("jitsi-meet-logger").getLogger(__filename);
3 3
 
4 4
 import {openConnection} from './connection';
5
-import Invite from './modules/UI/invite/Invite';
6 5
 import ContactList from './modules/UI/side_pannels/contactlist/ContactList';
7 6
 
8 7
 import AuthHandler from './modules/UI/authentication/AuthHandler';
@@ -28,7 +27,8 @@ import {
28 27
     conferenceFailed,
29 28
     conferenceJoined,
30 29
     conferenceLeft,
31
-    EMAIL_COMMAND
30
+    EMAIL_COMMAND,
31
+    lockStateChanged
32 32
 } from './react/features/base/conference';
33 33
 import {
34 34
     updateDeviceList
@@ -373,10 +373,9 @@ function createLocalTracks(options, checkForPermissionPrompt) {
373 373
 }
374 374
 
375 375
 class ConferenceConnector {
376
-    constructor(resolve, reject, invite) {
376
+    constructor(resolve, reject) {
377 377
         this._resolve = resolve;
378 378
         this._reject = reject;
379
-        this._invite = invite;
380 379
         this.reconnectTimeout = null;
381 380
         room.on(ConferenceEvents.CONFERENCE_JOINED,
382 381
             this._handleConferenceJoined.bind(this));
@@ -411,14 +410,17 @@ class ConferenceConnector {
411 410
 
412 411
             // not enough rights to create conference
413 412
         case ConferenceErrors.AUTHENTICATION_REQUIRED:
414
-            // schedule reconnect to check if someone else created the room
415
-            this.reconnectTimeout = setTimeout(function () {
416
-                room.join();
417
-            }, 5000);
418
-
419
-            // notify user that auth is required
420
-            AuthHandler.requireAuth(
421
-                room, this._invite.getRoomLocker().password);
413
+            {
414
+                // schedule reconnect to check if someone else created the room
415
+                this.reconnectTimeout = setTimeout(function () {
416
+                    room.join();
417
+                }, 5000);
418
+
419
+                const { password }
420
+                    = APP.store.getState()['features/base/conference'];
421
+
422
+                AuthHandler.requireAuth(room, password);
423
+            }
422 424
             break;
423 425
 
424 426
         case ConferenceErrors.RESERVATION_ERROR:
@@ -630,8 +632,7 @@ export default {
630 632
                 // XXX The API will take care of disconnecting from the XMPP
631 633
                 // server (and, thus, leaving the room) on unload.
632 634
                 return new Promise((resolve, reject) => {
633
-                    (new ConferenceConnector(
634
-                        resolve, reject, this.invite)).connect();
635
+                    (new ConferenceConnector(resolve, reject)).connect();
635 636
                 });
636 637
         });
637 638
     },
@@ -985,7 +986,6 @@ export default {
985 986
         room = connection.initJitsiConference(APP.conference.roomName,
986 987
             this._getConferenceOptions());
987 988
         this._setLocalAudioVideoStreams(localTracks);
988
-        this.invite = new Invite(room);
989 989
         this._room = room; // FIXME do not use this
990 990
 
991 991
         _setupLocalParticipantProperties();
@@ -1449,6 +1449,10 @@ export default {
1449 1449
             APP.UI.changeDisplayName(id, formattedDisplayName);
1450 1450
         });
1451 1451
 
1452
+        room.on(ConferenceEvents.LOCK_STATE_CHANGED, (...args) => {
1453
+            APP.store.dispatch(lockStateChanged(room, ...args));
1454
+        });
1455
+
1452 1456
         room.on(ConferenceEvents.PARTICIPANT_PROPERTY_CHANGED,
1453 1457
                 (participant, name, oldValue, newValue) => {
1454 1458
             if (name === "raisedHand") {

+ 2
- 0
css/_variables.scss 파일 보기

@@ -83,6 +83,8 @@ $rateStarSize: 34px;
83 83
  * Modals
84 84
  */
85 85
 $modalButtonFontSize: 14px;
86
+$modalMockAKInputBackground: #fafbfc;
87
+$modalMockAKInputBorder: 1px solid #f4f5f7;
86 88
 $modalTextColor: #333;
87 89
 
88 90
 /**

+ 6
- 0
css/modals/_dialog.scss 파일 보기

@@ -79,6 +79,12 @@
79 79
 
80 80
 .modal-dialog-form {
81 81
     color: $modalTextColor;
82
+
83
+    .input-control {
84
+        background: $modalMockAKInputBackground;
85
+        border: $modalMockAKInputBorder;
86
+        color: inherit;
87
+    }
82 88
 }
83 89
 .modal-dialog-footer {
84 90
     font-size: $modalButtonFontSize;

+ 41
- 1
css/modals/invite/_invite.scss 파일 보기

@@ -4,4 +4,44 @@
4 4
  */
5 5
 #inviteDialogRemovePassword {
6 6
     cursor: hand;
7
-}
7
+}
8
+
9
+.invite-dialog {
10
+    .form-control {
11
+        padding: 0;
12
+    }
13
+
14
+    .inviteLink {
15
+        color: $readOnlyInputColor;
16
+    }
17
+
18
+    .lock-state {
19
+        display: flex;
20
+    }
21
+
22
+    .password-overview {
23
+        .form-control {
24
+            margin-top: 10px;
25
+        }
26
+
27
+        .password-overview-toggle-edit,
28
+        .remove-password-link {
29
+            cursor: pointer;
30
+            text-decoration: none;
31
+        }
32
+
33
+        .password-overview-status,
34
+        .remove-password {
35
+            display: flex;
36
+            justify-content: space-between;
37
+        }
38
+
39
+        .remove-password {
40
+            margin-top: 15px;
41
+        }
42
+    }
43
+
44
+    .remove-password-current {
45
+        color: $inputControlEmColor;
46
+    }
47
+}

+ 9
- 4
lang/main.json 파일 보기

@@ -153,8 +153,7 @@
153 153
         "cameraAndMic": "Camera and microphone",
154 154
         "moderator": "MODERATOR",
155 155
         "password": "SET PASSWORD",
156
-        "audioVideo": "AUDIO AND VIDEO",
157
-        "setPasswordLabel": "Lock your room with a password."
156
+        "audioVideo": "AUDIO AND VIDEO"
158 157
     },
159 158
     "profile": {
160 159
         "title": "Profile",
@@ -225,8 +224,6 @@
225 224
         "connecting": "Connecting",
226 225
         "copy": "Copy",
227 226
         "error": "Error",
228
-        "roomLocked": "This call is locked. New callers must have the link and enter the password to join",
229
-        "addPassword": "Add a password",
230 227
         "createPassword": "Create password",
231 228
         "detectext": "Error when trying to detect desktopsharing extension.",
232 229
         "failtoinstall": "Failed to install desktop sharing extension",
@@ -429,5 +426,13 @@
429 426
         "noOtherDevices": "No other devices available",
430 427
         "selectADevice": "Select a device",
431 428
         "testAudio": "Test sound"
429
+    },
430
+    "invite": {
431
+        "addPassword": "Add password",
432
+        "hidePassword": "Hide password",
433
+        "inviteTo": "Invite people to __conferenceName__",
434
+        "locked": "This call is locked. New callers must have the link and enter the password to join.",
435
+        "showPassword": "Show password",
436
+        "unlocked": "This call is unlocked. Any new caller with the link may join the call."
432 437
     }
433 438
 }

+ 0
- 187
modules/UI/invite/Invite.js 파일 보기

@@ -1,187 +0,0 @@
1
-/* global JitsiMeetJS, APP */
2
-const logger = require("jitsi-meet-logger").getLogger(__filename);
3
-
4
-import InviteDialogView from './InviteDialogView';
5
-import createRoomLocker from './RoomLocker';
6
-import UIEvents from  '../../../service/UI/UIEvents';
7
-
8
-const ConferenceEvents = JitsiMeetJS.events.conference;
9
-
10
-/**
11
- * Invite module
12
- * Constructor takes conference object giving
13
- * ability to subscribe on its events
14
- */
15
-class Invite {
16
-    constructor(conference) {
17
-        this.conference = conference;
18
-        this.inviteUrl = APP.ConferenceUrl.getInviteUrl();
19
-        this.createRoomLocker(conference);
20
-        this.registerListeners();
21
-    }
22
-
23
-    /**
24
-     * Registering listeners.
25
-     * Primarily listeners for conference events.
26
-     */
27
-    registerListeners() {
28
-
29
-        this.conference.on(ConferenceEvents.LOCK_STATE_CHANGED,
30
-            (locked, error) => {
31
-
32
-            logger.log("Received channel password lock change: ", locked,
33
-                error);
34
-
35
-            if (!locked) {
36
-                this.getRoomLocker().resetPassword();
37
-            }
38
-
39
-            this.setLockedFromElsewhere(locked);
40
-        });
41
-
42
-        this.conference.on(ConferenceEvents.USER_ROLE_CHANGED, (id) => {
43
-            if (APP.conference.isLocalId(id)
44
-                    && this.isModerator !== this.conference.isModerator()) {
45
-
46
-                this.setModerator(this.conference.isModerator());
47
-            }
48
-        });
49
-
50
-        APP.UI.addListener( UIEvents.INVITE_CLICKED,
51
-                            () => { this.openLinkDialog(); });
52
-    }
53
-
54
-    /**
55
-     * Updates the view.
56
-     * If dialog hasn't been defined -
57
-     * creates it and updates
58
-     */
59
-    updateView() {
60
-        if (!this.view) {
61
-            this.initDialog();
62
-        }
63
-
64
-        this.view.updateView();
65
-    }
66
-
67
-    /**
68
-     * Room locker factory
69
-     * @param room
70
-     * @returns {Object} RoomLocker
71
-     * @factory
72
-     */
73
-    createRoomLocker(room = this.conference) {
74
-        let roomLocker = createRoomLocker(room);
75
-        this.roomLocker = roomLocker;
76
-        return this.getRoomLocker();
77
-    }
78
-
79
-    /**
80
-     * Room locker getter
81
-     * @returns {Object} RoomLocker
82
-     */
83
-    getRoomLocker() {
84
-        return this.roomLocker;
85
-    }
86
-
87
-    /**
88
-     * Opens the invite link dialog.
89
-     */
90
-    openLinkDialog () {
91
-        if (!this.view) {
92
-            this.initDialog();
93
-        }
94
-
95
-        this.view.open();
96
-    }
97
-
98
-    /**
99
-     * Dialog initialization.
100
-     * creating view object using as a model this module
101
-     */
102
-    initDialog() {
103
-        this.view = new InviteDialogView(this);
104
-    }
105
-
106
-    /**
107
-     * Password getter
108
-     * @returns {String} password
109
-     */
110
-    getPassword() {
111
-        return this.getRoomLocker().password;
112
-    }
113
-
114
-    /**
115
-     * Switches between the moderator view and normal view.
116
-     *
117
-     * @param isModerator indicates if the participant is moderator
118
-     */
119
-    setModerator(isModerator) {
120
-        this.isModerator = isModerator;
121
-
122
-        this.updateView();
123
-    }
124
-
125
-    /**
126
-     * Allows to unlock the room.
127
-     * If the current user is moderator.
128
-     */
129
-    setRoomUnlocked() {
130
-        if (this.isModerator) {
131
-            this.getRoomLocker().lock().then(() => {
132
-                APP.UI.emitEvent(UIEvents.TOGGLE_ROOM_LOCK);
133
-                this.updateView();
134
-            });
135
-        }
136
-    }
137
-
138
-    /**
139
-     * Allows to lock the room if
140
-     * the current user is moderator.
141
-     * Takes the password.
142
-     * @param {String} newPass
143
-     */
144
-    setRoomLocked(newPass) {
145
-        let isModerator = this.isModerator;
146
-        if (isModerator && (newPass || !this.getRoomLocker().isLocked)) {
147
-            this.getRoomLocker().lock(newPass).then(() => {
148
-                APP.UI.emitEvent(UIEvents.TOGGLE_ROOM_LOCK);
149
-                this.updateView();
150
-            });
151
-        }
152
-    }
153
-
154
-    /**
155
-     * Helper method for encoding
156
-     * Invite URL
157
-     * @returns {string}
158
-     */
159
-    getEncodedInviteUrl() {
160
-        return encodeURI(this.inviteUrl);
161
-    }
162
-
163
-    /**
164
-     * Is locked flag.
165
-     * Delegates to room locker
166
-     * @returns {Boolean} isLocked
167
-     */
168
-    isLocked() {
169
-        return this.getRoomLocker().isLocked;
170
-    }
171
-
172
-    /**
173
-     * Set flag locked from elsewhere to room locker.
174
-     * @param isLocked
175
-     */
176
-    setLockedFromElsewhere(isLocked) {
177
-        let roomLocker = this.getRoomLocker();
178
-        let oldLockState = roomLocker.isLocked;
179
-        if (oldLockState !== isLocked) {
180
-            roomLocker.lockedElsewhere = isLocked;
181
-            APP.UI.emitEvent(UIEvents.TOGGLE_ROOM_LOCK);
182
-            this.updateView();
183
-        }
184
-    }
185
-}
186
-
187
-export default Invite;

+ 0
- 378
modules/UI/invite/InviteDialogView.js 파일 보기

@@ -1,378 +0,0 @@
1
-/* global $, APP, JitsiMeetJS */
2
-const logger = require("jitsi-meet-logger").getLogger(__filename);
3
-
4
-/**
5
- * Substate for password
6
- * @type {{LOCKED: string, UNLOCKED: string}}
7
- */
8
-const States = {
9
-    LOCKED: 'locked',
10
-    UNLOCKED: 'unlocked'
11
-};
12
-
13
-/**
14
- * Class representing view for Invite dialog
15
- * @class InviteDialogView
16
- */
17
-export default class InviteDialogView {
18
-    constructor(model) {
19
-        this.unlockHint = "unlockHint";
20
-        this.lockHint = "lockHint";
21
-        this.model = model;
22
-
23
-        if (this.model.inviteUrl === null) {
24
-            this.inviteAttributes = `data-i18n="[value]inviteUrlDefaultMsg"`;
25
-        } else {
26
-            this.inviteAttributes
27
-                = `value="${this.model.getEncodedInviteUrl()}"`;
28
-        }
29
-
30
-        this.initDialog();
31
-    }
32
-
33
-    /**
34
-     * Initialization of dialog property
35
-     */
36
-    initDialog() {
37
-        let dialog = {};
38
-        dialog.closeFunction = this.closeFunction.bind(this);
39
-        dialog.submitFunction = this.submitFunction.bind(this);
40
-        dialog.loadedFunction = this.loadedFunction.bind(this);
41
-
42
-        dialog.titleKey = "dialog.shareLink";
43
-        this.dialog = dialog;
44
-
45
-        this.dialog.states = this.getStates();
46
-    }
47
-
48
-    /**
49
-     * Event handler for submitting dialog
50
-     * @param e
51
-     * @param v
52
-     */
53
-    submitFunction(e, v) {
54
-        if (v && this.model.inviteUrl) {
55
-            JitsiMeetJS.analytics.sendEvent('toolbar.invite.button');
56
-        } else {
57
-            JitsiMeetJS.analytics.sendEvent('toolbar.invite.cancel');
58
-        }
59
-    }
60
-
61
-    /**
62
-     * Event handler for load dialog
63
-     * @param event
64
-     */
65
-    loadedFunction(event) {
66
-        if (this.model.inviteUrl) {
67
-            document.getElementById('inviteLinkRef').select();
68
-        } else {
69
-            if (event && event.target) {
70
-                $(event.target).find('button[value=true]')
71
-                    .prop('disabled', true);
72
-            }
73
-        }
74
-    }
75
-
76
-    /**
77
-     * Event handler for closing dialog
78
-     * @param e
79
-     * @param v
80
-     * @param m
81
-     * @param f
82
-     */
83
-    closeFunction(e, v, m, f) {
84
-        $(document).off('click', '.copyInviteLink', this.copyToClipboard);
85
-
86
-        if(!v && !m && !f)
87
-            JitsiMeetJS.analytics.sendEvent('toolbar.invite.close');
88
-    }
89
-
90
-    /**
91
-     * Returns all states of the dialog
92
-     * @returns {{}}
93
-     */
94
-    getStates() {
95
-        let {
96
-            titleKey
97
-        } = this.dialog;
98
-        let doneMsg = APP.translation.generateTranslationHTML('dialog.done');
99
-        let states = {};
100
-        let buttons = {};
101
-        buttons[`${doneMsg}`] = true;
102
-
103
-        states[States.UNLOCKED] = {
104
-            titleKey,
105
-            html: this.getShareLinkBlock() + this.getAddPasswordBlock(),
106
-            buttons
107
-        };
108
-        states[States.LOCKED] = {
109
-            titleKey,
110
-            html: this.getShareLinkBlock() + this.getPasswordBlock(),
111
-            buttons
112
-        };
113
-
114
-        return states;
115
-    }
116
-
117
-    /**
118
-     * Layout for invite link input
119
-     * @returns {string}
120
-     */
121
-    getShareLinkBlock() {
122
-        let classes = 'button-control button-control_light copyInviteLink';
123
-        return (
124
-            `<div class="form-control">
125
-                <label class="form-control__label" for="inviteLinkRef"
126
-                    data-i18n="${this.dialog.titleKey}"></label>
127
-                <div class="form-control__container">
128
-                    <input class="input-control inviteLink"
129
-                           id="inviteLinkRef" type="text"
130
-                           ${this.inviteAttributes} readonly>
131
-                    <button data-i18n="dialog.copy" class="${classes}"></button>
132
-                </div>
133
-                <p class="form-control__hint ${this.lockHint}">
134
-                   <span class="icon-security-locked"></span>
135
-                   <span data-i18n="dialog.roomLocked"></span>
136
-                </p>
137
-                <p class="form-control__hint ${this.unlockHint}">
138
-                   <span class="icon-security"></span>
139
-                   <span data-i18n="roomUnlocked"></span>
140
-                </p>
141
-            </div>`
142
-        );
143
-    }
144
-
145
-    /**
146
-     * Layout for adding password input
147
-     * @returns {string}
148
-     */
149
-    getAddPasswordBlock() {
150
-        let html;
151
-
152
-        if (this.model.isModerator) {
153
-            html = (`
154
-            <div class="form-control">
155
-                <label class="form-control__label"
156
-                       for="newPasswordInput" data-i18n="dialog.addPassword">
157
-               </label>
158
-                <div class="form-control__container">
159
-                    <input class="input-control"
160
-                           id="newPasswordInput"
161
-                           type="text" 
162
-                           data-i18n="[placeholder]dialog.createPassword">
163
-                    <button id="addPasswordBtn" id="inviteDialogAddPassword"
164
-                            disabled data-i18n="dialog.add"
165
-                            class="button-control button-control_light">
166
-                    </button>
167
-                </div>
168
-            </div>
169
-        `);
170
-        } else {
171
-            html = '';
172
-        }
173
-
174
-        return html;
175
-    }
176
-
177
-    /**
178
-     * Layout for password (when room is locked)
179
-     * @returns {string}
180
-     */
181
-    getPasswordBlock() {
182
-        let password = this.model.getPassword();
183
-        let { isModerator } = this.model;
184
-
185
-        if (isModerator) {
186
-            return (`
187
-                <div class="form-control">
188
-                    <label class="form-control__label"
189
-                           data-i18n="dialog.passwordLabel"></label>
190
-                    <div class="form-control__container">
191
-                        <p>
192
-                            <span class="form-control__text"
193
-                                  data-i18n="dialog.currentPassword"></span>
194
-                            <span id="inviteDialogPassword"
195
-                                  class="form-control__em">
196
-                                ${password}
197
-                            </span>
198
-                        </p>
199
-                        <a class="link form-control__right"
200
-                           id="inviteDialogRemovePassword"
201
-                           data-i18n="dialog.removePassword"></a>
202
-                    </div>
203
-                </div>
204
-            `);
205
-        } else {
206
-            return (`
207
-                <div class="form-control">
208
-                    <p>A participant protected this call with a password.</p>
209
-                </div>
210
-            `);
211
-        }
212
-
213
-    }
214
-
215
-
216
-
217
-    /**
218
-     * Opening the dialog
219
-     */
220
-    open() {
221
-        let {
222
-            submitFunction,
223
-            loadedFunction,
224
-            closeFunction
225
-        } = this.dialog;
226
-
227
-        let states = this.getStates();
228
-        let initial = this.model.roomLocked ? States.LOCKED : States.UNLOCKED;
229
-
230
-        APP.UI.messageHandler.openDialogWithStates(states, {
231
-            submit: submitFunction,
232
-            loaded: loadedFunction,
233
-            close: closeFunction,
234
-            size: 'medium'
235
-        });
236
-        $.prompt.goToState(initial);
237
-
238
-        this.registerListeners();
239
-        this.updateView();
240
-    }
241
-
242
-    /**
243
-     * Setting event handlers
244
-     * used in dialog
245
-     */
246
-    registerListeners() {
247
-        const ENTER_KEY = 13;
248
-        let addPasswordBtn = '#addPasswordBtn';
249
-        let copyInviteLink = '.copyInviteLink';
250
-        let newPasswordInput = '#newPasswordInput';
251
-        let removePassword = '#inviteDialogRemovePassword';
252
-
253
-        $(document).on('click', copyInviteLink, this.copyToClipboard);
254
-        $(removePassword).on('click', () => {
255
-            this.model.setRoomUnlocked();
256
-        });
257
-        let boundSetPassword = this._setPassword.bind(this);
258
-        $(document).on('click', addPasswordBtn, boundSetPassword);
259
-        let boundDisablePass = this.disableAddPassIfInputEmpty.bind(this);
260
-        $(document).on('keypress', newPasswordInput, boundDisablePass);
261
-
262
-        // We need to handle keydown event because impromptu
263
-        // is listening to it too for closing the dialog
264
-        $(newPasswordInput).on('keydown', (e) => {
265
-            if (e.keyCode === ENTER_KEY) {
266
-                e.stopPropagation();
267
-                this._setPassword();
268
-            }
269
-        });
270
-    }
271
-
272
-    /**
273
-     * Marking room as locked
274
-     * @private
275
-     */
276
-    _setPassword() {
277
-        let $passInput = $('#newPasswordInput');
278
-        let newPass = $passInput.val();
279
-
280
-        if(newPass) {
281
-            this.model.setRoomLocked(newPass);
282
-        }
283
-    }
284
-
285
-    /**
286
-     * Checking input and if it's empty then
287
-     * disable add pass button
288
-     */
289
-    disableAddPassIfInputEmpty() {
290
-        let $passInput = $('#newPasswordInput');
291
-        let $addPassBtn = $('#addPasswordBtn');
292
-
293
-        if(!$passInput.val()) {
294
-            $addPassBtn.prop('disabled', true);
295
-        } else {
296
-            $addPassBtn.prop('disabled', false);
297
-        }
298
-    }
299
-
300
-    /**
301
-     * Copying text to clipboard
302
-     */
303
-    copyToClipboard() {
304
-        $('.inviteLink').each(function () {
305
-            let $el = $(this).closest('.jqistate');
306
-
307
-            // TOFIX: We can select only visible elements
308
-            if($el.css('display') === 'block') {
309
-                this.select();
310
-
311
-                try {
312
-                    document.execCommand('copy');
313
-                    this.blur();
314
-                }
315
-                catch (err) {
316
-                    logger.error('error when copy the text');
317
-                }
318
-            }
319
-        });
320
-    }
321
-
322
-    /**
323
-     * Method syncing the view and the model
324
-     */
325
-    updateView() {
326
-        let pass = this.model.getPassword();
327
-        let { isModerator } = this.model;
328
-        if (this.model.getRoomLocker().lockedElsewhere || !pass) {
329
-            $('#inviteDialogPassword').attr("data-i18n", "passwordSetRemotely");
330
-            APP.translation.translateElement($('#inviteDialogPassword'));
331
-        } else {
332
-            $('#inviteDialogPassword').removeAttr("data-i18n");
333
-            $('#inviteDialogPassword').text(pass);
334
-        }
335
-
336
-        // if we are not moderator we cannot remove password
337
-        if (isModerator)
338
-            $('#inviteDialogRemovePassword').show();
339
-        else
340
-            $('#inviteDialogRemovePassword').hide();
341
-
342
-        $('#newPasswordInput').val('');
343
-        this.disableAddPassIfInputEmpty();
344
-
345
-        this.updateInviteLink();
346
-
347
-        $.prompt.goToState(
348
-            (this.model.isLocked())
349
-                ? States.LOCKED
350
-                : States.UNLOCKED);
351
-
352
-        let roomLocked = `.${this.lockHint}`;
353
-        let roomUnlocked = `.${this.unlockHint}`;
354
-
355
-        let showDesc = this.model.isLocked() ? roomLocked : roomUnlocked;
356
-        let hideDesc = !this.model.isLocked() ? roomLocked : roomUnlocked;
357
-
358
-        $(showDesc).show();
359
-        $(hideDesc).hide();
360
-    }
361
-
362
-    /**
363
-     * Updates invite link
364
-     */
365
-    updateInviteLink() {
366
-        // If the invite dialog has been already opened we update the
367
-        // information.
368
-        let inviteLink = document.querySelectorAll('.inviteLink');
369
-        let list = Array.from(inviteLink);
370
-        list.forEach((inviteLink) => {
371
-            inviteLink.value = this.model.inviteUrl;
372
-            inviteLink.select();
373
-        });
374
-
375
-        $('#inviteLinkRef').parent()
376
-            .find('button[value=true]').prop('disabled', false);
377
-    }
378
-}

+ 0
- 103
modules/UI/invite/RoomLocker.js 파일 보기

@@ -1,103 +0,0 @@
1
-/* global APP, JitsiMeetJS */
2
-const logger = require("jitsi-meet-logger").getLogger(__filename);
3
-
4
-/**
5
- * Show notification that user cannot set password for the conference
6
- * because server doesn't support that.
7
- */
8
-function notifyPasswordNotSupported () {
9
-    logger.warn('room passwords not supported');
10
-    APP.UI.messageHandler.showError(
11
-        "dialog.warning", "dialog.passwordNotSupported");
12
-}
13
-
14
-/**
15
- * Show notification that setting password for the conference failed.
16
- * @param {Error} err error
17
- */
18
-function notifyPasswordFailed(err) {
19
-    logger.warn('setting password failed', err);
20
-    APP.UI.messageHandler.showError(
21
-        "dialog.lockTitle", "dialog.lockMessage");
22
-}
23
-
24
-const ConferenceErrors = JitsiMeetJS.errors.conference;
25
-
26
-/**
27
- * Create new RoomLocker for the conference.
28
- * It allows to set or remove password for the conference,
29
- * or ask for required password.
30
- * @returns {RoomLocker}
31
- */
32
-export default function createRoomLocker (room) {
33
-    let password;
34
-    /**
35
-     * If the room was locked from someone other than us, we indicate it with
36
-     * this property in order to have correct roomLocker state of isLocked.
37
-     * @type {boolean} whether room is locked, but not from us.
38
-     */
39
-    let lockedElsewhere = false;
40
-
41
-    /**
42
-     * @class RoomLocker
43
-     */
44
-    return {
45
-        get isLocked () {
46
-            return !!password || lockedElsewhere;
47
-        },
48
-
49
-        get password () {
50
-            return password;
51
-        },
52
-
53
-        /**
54
-         * Allows to set new password
55
-         * @param newPass
56
-         * @returns {Promise.<TResult>}
57
-         */
58
-        lock (newPass) {
59
-            return room.lock(newPass).then(() => {
60
-                password = newPass;
61
-                // If the password is undefined this means that we're removing
62
-                // it for everyone.
63
-                if (!password)
64
-                    lockedElsewhere = false;
65
-            }).catch(function (err) {
66
-                logger.error(err);
67
-                if (err === ConferenceErrors.PASSWORD_NOT_SUPPORTED) {
68
-                    notifyPasswordNotSupported();
69
-                } else {
70
-                    notifyPasswordFailed(err);
71
-                }
72
-                throw err;
73
-            });
74
-        },
75
-
76
-        /**
77
-         * Sets that the room is locked from another user, not us.
78
-         * @param {boolean} value locked/unlocked state
79
-         */
80
-        set lockedElsewhere (value) {
81
-            lockedElsewhere = value;
82
-        },
83
-
84
-        /**
85
-         * Whether room is locked from someone else.
86
-         * @returns {boolean} whether room is not locked locally,
87
-         * but it is still locked.
88
-         */
89
-        get lockedElsewhere () {
90
-            return lockedElsewhere;
91
-        },
92
-
93
-        /**
94
-         * Reset the password. Can be useful when room
95
-         * has been unlocked from elsewhere and we can use
96
-         * this method for sync the pass
97
-         */
98
-        resetPassword() {
99
-            password = null;
100
-        },
101
-
102
-    };
103
-}

+ 4
- 4
modules/UI/side_pannels/contactlist/ContactList.js 파일 보기

@@ -19,14 +19,14 @@ class ContactList {
19 19
     }
20 20
 
21 21
     /**
22
-     * Is locked flag.
23
-     * Delegates to Invite module
24
-     * TO FIX: find a better way to access the IS LOCKED state of the invite.
22
+     * Returns true if the current conference is locked.
25 23
      *
26 24
      * @returns {Boolean}
27 25
      */
28 26
     isLocked() {
29
-        return APP.conference.invite.isLocked();
27
+        const conference = APP.store.getState()['features/base/conference'];
28
+
29
+        return conference.locked;
30 30
     }
31 31
 
32 32
     /**

+ 16
- 14
modules/UI/side_pannels/contactlist/ContactListView.js 파일 보기

@@ -1,6 +1,8 @@
1 1
 /* global $, APP, interfaceConfig */
2 2
 const logger = require("jitsi-meet-logger").getLogger(__filename);
3 3
 
4
+import { openInviteDialog } from '../../../../react/features/invite';
5
+
4 6
 import Avatar from '../../avatar/Avatar';
5 7
 import UIEvents from '../../../../service/UI/UIEvents';
6 8
 import UIUtil from '../../util/UIUtil';
@@ -96,7 +98,7 @@ var ContactListView = {
96 98
         this.model = model;
97 99
         this.addInviteButton();
98 100
         this.registerListeners();
99
-        this.toggleLock();
101
+        this.setLockDisplay(false);
100 102
     },
101 103
     /**
102 104
      * Adds layout for invite button
@@ -108,8 +110,9 @@ var ContactListView = {
108 110
             .insertAdjacentHTML('afterend', this.getInviteButtonLayout());
109 111
 
110 112
         APP.translation.translateElement($(container));
113
+
111 114
         $(document).on('click', '#addParticipantsBtn', () => {
112
-            APP.UI.emitEvent(UIEvents.INVITE_CLICKED);
115
+            APP.store.dispatch(openInviteDialog());
113 116
         });
114 117
     },
115 118
     /**
@@ -125,8 +128,8 @@ var ContactListView = {
125 128
 
126 129
         return (
127 130
             `<div class="sideToolbarBlock first">
128
-                <button id="addParticipantsBtn" 
129
-                         data-i18n="${key}" 
131
+                <button id="addParticipantsBtn"
132
+                         data-i18n="${key}"
130 133
                          class="${classes}"></button>
131 134
                 <div>
132 135
                     ${lockedHtml}
@@ -158,7 +161,7 @@ var ContactListView = {
158 161
         let displayNameChange = this.onDisplayNameChange.bind(this);
159 162
 
160 163
         APP.UI.addListener( UIEvents.TOGGLE_ROOM_LOCK,
161
-                            this.toggleLock.bind(this));
164
+                            this.setLockDisplay.bind(this));
162 165
         APP.UI.addListener( UIEvents.CONTACT_ADDED,
163 166
                             this.onAddContact.bind(this));
164 167
 
@@ -167,16 +170,15 @@ var ContactListView = {
167 170
         APP.UI.addListener(UIEvents.DISPLAY_NAME_CHANGED, displayNameChange);
168 171
     },
169 172
     /**
170
-     * Updating the view according the model
171
-     * @param type {String} type of change
172
-     * @returns {Promise}
173
+     * Updates the view according to the passed in lock state.
174
+     *
175
+     * @param {boolean} isLocked - True if the locked UI state should display.
173 176
      */
174
-    toggleLock() {
175
-        let isLocked = this.model.isLocked();
176
-        let showKey = isLocked ? this.lockKey : this.unlockKey;
177
-        let hideKey = !isLocked ? this.lockKey : this.unlockKey;
178
-        let showId = `contactList${showKey}`;
179
-        let hideId = `contactList${hideKey}`;
177
+    setLockDisplay(isLocked) {
178
+        const showKey = isLocked ? this.lockKey : this.unlockKey;
179
+        const hideKey = !isLocked ? this.lockKey : this.unlockKey;
180
+        const showId = `contactList${showKey}`;
181
+        const hideId = `contactList${hideKey}`;
180 182
 
181 183
         $(`#${showId}`).show();
182 184
         $(`#${hideId}`).hide();

+ 11
- 0
react/features/base/conference/actionTypes.js 파일 보기

@@ -116,6 +116,17 @@ export const SET_LASTN = Symbol('SET_LASTN');
116 116
  */
117 117
 export const SET_PASSWORD = Symbol('SET_PASSWORD');
118 118
 
119
+/**
120
+ * The type of Redux action which signals that setting a password on a
121
+ * JitsiConference encountered an error and failed.
122
+ *
123
+ * {
124
+ *     type: SET_PASSWORD_FAILED,
125
+ *     error: string
126
+ * }
127
+ */
128
+export const SET_PASSWORD_FAILED = Symbol('SET_PASSWORD_FAILED');
129
+
119 130
 /**
120 131
  * The type of the Redux action which sets the name of the room of the
121 132
  * conference to be joined.

+ 9
- 3
react/features/base/conference/actions.js 파일 보기

@@ -22,6 +22,7 @@ import {
22 22
     _SET_AUDIO_ONLY_VIDEO_MUTED,
23 23
     SET_LASTN,
24 24
     SET_PASSWORD,
25
+    SET_PASSWORD_FAILED,
25 26
     SET_ROOM
26 27
 } from './actionTypes';
27 28
 import {
@@ -56,7 +57,7 @@ function _addConferenceListeners(conference, dispatch) {
56 57
 
57 58
     conference.on(
58 59
             JitsiConferenceEvents.LOCK_STATE_CHANGED,
59
-            (...args) => dispatch(_lockStateChanged(conference, ...args)));
60
+            (...args) => dispatch(lockStateChanged(conference, ...args)));
60 61
 
61 62
     // Dispatches into features/base/tracks follow:
62 63
 
@@ -292,7 +293,7 @@ export function createConference() {
292 293
  *     locked: boolean
293 294
  * }}
294 295
  */
295
-function _lockStateChanged(conference, locked) {
296
+export function lockStateChanged(conference, locked) {
296 297
     return {
297 298
         type: LOCK_STATE_CHANGED,
298 299
         conference,
@@ -440,7 +441,12 @@ export function setPassword(conference, method, password) {
440 441
                             conference,
441 442
                             method,
442 443
                             password
443
-                        })));
444
+                        }))
445
+                        .catch(error => dispatch({
446
+                            type: SET_PASSWORD_FAILED,
447
+                            error
448
+                        }))
449
+                );
444 450
             }
445 451
 
446 452
             return Promise.reject();

+ 25
- 4
react/features/base/conference/reducer.js 파일 보기

@@ -1,3 +1,5 @@
1
+import { LOCKED_LOCALLY, LOCKED_REMOTELY } from '../../room-lock';
2
+
1 3
 import { JitsiConferenceErrors } from '../lib-jitsi-meet';
2 4
 import {
3 5
     ReducerRegistry,
@@ -83,7 +85,7 @@ function _conferenceFailed(state, action) {
83 85
             audioOnlyVideoMuted: undefined,
84 86
             conference: undefined,
85 87
             leaving: undefined,
86
-            locked: undefined,
88
+            locked: passwordRequired ? LOCKED_REMOTELY : undefined,
87 89
             password: undefined,
88 90
 
89 91
             /**
@@ -112,7 +114,7 @@ function _conferenceJoined(state, action) {
112 114
     // i.e. password-protected is private to lib-jitsi-meet. However, the
113 115
     // library does not fire LOCK_STATE_CHANGED upon joining a JitsiConference
114 116
     // with a password.
115
-    const locked = conference.room.locked || undefined;
117
+    const locked = conference.room.locked ? LOCKED_REMOTELY : undefined;
116 118
 
117 119
     return (
118 120
         setStateProperties(state, {
@@ -209,7 +211,16 @@ function _lockStateChanged(state, action) {
209 211
         return state;
210 212
     }
211 213
 
212
-    return setStateProperty(state, 'locked', action.locked || undefined);
214
+    let lockState;
215
+
216
+    if (action.locked) {
217
+        lockState = state.locked || LOCKED_REMOTELY;
218
+    }
219
+
220
+    return setStateProperties(state, {
221
+        locked: lockState,
222
+        password: action.locked ? state.password : null
223
+    });
213 224
 }
214 225
 
215 226
 /**
@@ -254,10 +265,12 @@ function _setPassword(state, action) {
254 265
     const conference = action.conference;
255 266
 
256 267
     switch (action.method) {
257
-    case conference.join:
268
+    case conference.join: {
258 269
         if (state.passwordRequired === conference) {
259 270
             return (
260 271
                 setStateProperties(state, {
272
+                    locked: LOCKED_REMOTELY,
273
+
261 274
                     /**
262 275
                      * The password with which the conference is to be joined.
263 276
                      *
@@ -267,8 +280,16 @@ function _setPassword(state, action) {
267 280
                     passwordRequired: undefined
268 281
                 }));
269 282
         }
283
+
270 284
         break;
271 285
     }
286
+    case conference.lock: {
287
+        return setStateProperties(state, {
288
+            locked: action.password ? LOCKED_LOCALLY : undefined,
289
+            password: action.password
290
+        });
291
+    }
292
+    }
272 293
 
273 294
     return state;
274 295
 }

+ 18
- 0
react/features/invite/actions.js 파일 보기

@@ -0,0 +1,18 @@
1
+/* globals APP */
2
+
3
+import { openDialog } from '../../features/base/dialog';
4
+
5
+import { InviteDialog } from './components';
6
+
7
+/**
8
+ * Opens the Invite Dialog.
9
+ *
10
+ * @returns {Function}
11
+ */
12
+export function openInviteDialog() {
13
+    return dispatch => {
14
+        dispatch(openDialog(InviteDialog, {
15
+            conferenceUrl: encodeURI(APP.ConferenceUrl.getInviteUrl())
16
+        }));
17
+    };
18
+}

+ 137
- 0
react/features/invite/components/AddPasswordForm.js 파일 보기

@@ -0,0 +1,137 @@
1
+import React, { Component } from 'react';
2
+import { connect } from 'react-redux';
3
+
4
+import { setPassword } from '../../base/conference';
5
+import { translate } from '../../base/i18n';
6
+
7
+/**
8
+ * A React Component for locking a JitsiConference with a password.
9
+ */
10
+class AddPasswordForm extends Component {
11
+    /**
12
+     * AddPasswordForm component's property types.
13
+     *
14
+     * @static
15
+     */
16
+    static propTypes = {
17
+        /**
18
+         * The JitsiConference on which to lock and set a password.
19
+         *
20
+         * @type {JitsiConference}
21
+         */
22
+        conference: React.PropTypes.object,
23
+
24
+        /**
25
+         * Invoked to set a password on the conference.
26
+         */
27
+        dispatch: React.PropTypes.func,
28
+
29
+        /**
30
+         * Invoked to obtain translated strings.
31
+         */
32
+        t: React.PropTypes.func
33
+    }
34
+
35
+    /**
36
+     * Initializes a new AddPasswordForm instance.
37
+     *
38
+     * @param {Object} props - The read-only properties with which the new
39
+     * instance is to be initialized.
40
+     */
41
+    constructor(props) {
42
+        super(props);
43
+
44
+        this.state = {
45
+            password: ''
46
+        };
47
+
48
+        this._onKeyDown = this._onKeyDown.bind(this);
49
+        this._onPasswordChange = this._onPasswordChange.bind(this);
50
+        this._onSubmit = this._onSubmit.bind(this);
51
+    }
52
+
53
+    /**
54
+     * Implements React's {@link Component#render()}.
55
+     *
56
+     * @inheritdoc
57
+     * @returns {ReactElement}
58
+     */
59
+    render() {
60
+        return (
61
+            <div
62
+                className = 'form-control'
63
+                onSubmit = { this._onSubmit } >
64
+                <div className = 'form-control__container'>
65
+                    <input
66
+                        autoFocus = { true }
67
+                        className = 'input-control'
68
+                        id = 'newPasswordInput'
69
+                        onChange = { this._onPasswordChange }
70
+                        onKeyDown = { this._onKeyDown }
71
+                        placeholder
72
+                            = { this.props.t('dialog.createPassword') }
73
+                        type = 'text' />
74
+                    <button
75
+                        className = 'button-control button-control_light'
76
+                        disabled = { !this.state.password }
77
+                        id = 'addPasswordBtn'
78
+                        onClick = { this._onSubmit }
79
+                        type = 'button'>
80
+                        { this.props.t('dialog.add') }
81
+                    </button>
82
+                </div>
83
+            </div>
84
+        );
85
+    }
86
+
87
+    /**
88
+     * Mimics form behavior by listening for enter key press and submitting the
89
+     * entered password.
90
+     *
91
+     * @param {Object} event - DOM Event for keydown.
92
+     * @private
93
+     * @returns {void}
94
+     */
95
+    _onKeyDown(event) {
96
+        event.stopPropagation();
97
+
98
+        if (event.keyCode === /* Enter */ 13) {
99
+            this._onSubmit();
100
+        }
101
+    }
102
+
103
+    /**
104
+     * Updates the internal state of the entered password.
105
+     *
106
+     * @param {Object} event - DOM Event for value change.
107
+     * @private
108
+     * @returns {void}
109
+     */
110
+    _onPasswordChange(event) {
111
+        this.setState({ password: event.target.value });
112
+    }
113
+
114
+    /**
115
+     * Dispatches a request to lock the conference with a password.
116
+     *
117
+     * @private
118
+     * @returns {void}
119
+     */
120
+    _onSubmit() {
121
+        if (!this.state.password) {
122
+            return;
123
+        }
124
+
125
+        const conference = this.props.conference;
126
+
127
+        this.props.dispatch(setPassword(
128
+            conference,
129
+            conference.lock,
130
+            this.state.password
131
+        ));
132
+
133
+        this.setState({ password: '' });
134
+    }
135
+}
136
+
137
+export default translate(connect()(AddPasswordForm));

+ 104
- 0
react/features/invite/components/InviteDialog.js 파일 보기

@@ -0,0 +1,104 @@
1
+import React, { Component } from 'react';
2
+import { connect } from 'react-redux';
3
+
4
+import { Dialog } from '../../base/dialog';
5
+import { translate } from '../../base/i18n';
6
+import JitsiMeetJS from '../../base/lib-jitsi-meet';
7
+import {
8
+    getLocalParticipant,
9
+    PARTICIPANT_ROLE
10
+} from '../../base/participants';
11
+
12
+import PasswordContainer from './PasswordContainer';
13
+import ShareLinkForm from './ShareLinkForm';
14
+
15
+/**
16
+ * A React Component for displaying other components responsible for copying the
17
+ * current conference url and for setting or removing a conference password.
18
+ */
19
+class InviteDialog extends Component {
20
+    /**
21
+     * InviteDialog component's property types.
22
+     *
23
+     * @static
24
+     */
25
+    static propTypes = {
26
+        /**
27
+         * The redux store representation of the JitsiConference.
28
+         *
29
+         */
30
+        _conference: React.PropTypes.object,
31
+
32
+        /**
33
+         * Whether or not the current user is a conference moderator.
34
+         */
35
+        _isModerator: React.PropTypes.bool,
36
+
37
+        /**
38
+         * The url for the JitsiConference.
39
+         */
40
+        conferenceUrl: React.PropTypes.string,
41
+
42
+        /**
43
+         * Invoked to obtain translated strings.
44
+         */
45
+        t: React.PropTypes.func
46
+    }
47
+
48
+    /**
49
+     * Reports an analytics event for the invite modal being closed.
50
+     *
51
+     * @inheritdoc
52
+     */
53
+    componentWillUnmount() {
54
+        JitsiMeetJS.analytics.sendEvent('toolbar.invite.close');
55
+    }
56
+
57
+    /**
58
+     * Implements React's {@link Component#render()}.
59
+     *
60
+     * @inheritdoc
61
+     * @returns {ReactElement}
62
+     */
63
+    render() {
64
+        return (
65
+            <Dialog
66
+                cancelDisabled = { true }
67
+                okTitleKey = 'dialog.done'
68
+                titleString = { this.props.t(
69
+                    'invite.inviteTo',
70
+                    { conferenceName: this.props._conference.room }) } >
71
+                <div className = 'invite-dialog'>
72
+                    <ShareLinkForm toCopy = { this.props.conferenceUrl } />
73
+                    <PasswordContainer
74
+                        conference = { this.props._conference.conference }
75
+                        locked = { this.props._conference.locked }
76
+                        password = { this.props._conference.password }
77
+                        showPasswordEdit = { this.props._isModerator } />
78
+                </div>
79
+            </Dialog>
80
+        );
81
+    }
82
+}
83
+
84
+/**
85
+ * Maps (parts of) the Redux state to the associated InviteDialog's props.
86
+ *
87
+ * @param {Object} state - The Redux state.
88
+ * @private
89
+ * @returns {{
90
+ *     _conference: Object,
91
+ *     _isModerator: boolean
92
+ * }}
93
+ */
94
+function _mapStateToProps(state) {
95
+    const { role }
96
+        = getLocalParticipant(state['features/base/participants']);
97
+
98
+    return {
99
+        _conference: state['features/base/conference'],
100
+        _isModerator: role === PARTICIPANT_ROLE.MODERATOR
101
+    };
102
+}
103
+
104
+export default translate(connect(_mapStateToProps)(InviteDialog));

+ 48
- 0
react/features/invite/components/LockStatePanel.js 파일 보기

@@ -0,0 +1,48 @@
1
+import React, { Component } from 'react';
2
+
3
+import { translate } from '../../base/i18n';
4
+
5
+/**
6
+ * A React Component for displaying the conference lock state.
7
+ */
8
+class LockStatePanel extends Component {
9
+    /**
10
+     * LockStatePanel component's property types.
11
+     *
12
+     * @static
13
+     */
14
+    static propTypes = {
15
+        /**
16
+         * Whether or not the conference is currently locked.
17
+         */
18
+        locked: React.PropTypes.bool,
19
+
20
+        /**
21
+         * Invoked to obtain translated strings.
22
+         */
23
+        t: React.PropTypes.func
24
+    }
25
+
26
+    /**
27
+     * Implements React's {@link Component#render()}.
28
+     *
29
+     * @inheritdoc
30
+     * @returns {ReactElement}
31
+     */
32
+    render() {
33
+        const [ lockStateClass, lockIconClass, lockTextKey ] = this.props.locked
34
+            ? [ 'is-locked', 'icon-security-locked', 'invite.locked' ]
35
+            : [ 'is-unlocked', 'icon-security', 'invite.unlocked' ];
36
+
37
+        return (
38
+            <div className = { `lock-state ${lockStateClass}` }>
39
+                <span className = { lockIconClass } />
40
+                <span>
41
+                    { this.props.t(lockTextKey) }
42
+                </span>
43
+            </div>
44
+        );
45
+    }
46
+}
47
+
48
+export default translate(LockStatePanel);

+ 147
- 0
react/features/invite/components/PasswordContainer.js 파일 보기

@@ -0,0 +1,147 @@
1
+import React, { Component } from 'react';
2
+
3
+import { translate } from '../../base/i18n';
4
+import { LOCKED_LOCALLY } from '../../room-lock';
5
+
6
+import AddPasswordForm from './AddPasswordForm';
7
+import LockStatePanel from './LockStatePanel';
8
+import RemovePasswordForm from './RemovePasswordForm';
9
+
10
+/**
11
+ * React component for displaying the current room lock state as well as
12
+ * exposing features to modify the room lock.
13
+ */
14
+class PasswordContainer extends Component {
15
+    /**
16
+     * PasswordContainer component's property types.
17
+     *
18
+     * @static
19
+     */
20
+    static propTypes = {
21
+        /**
22
+         * The JitsiConference for which to display a lock state and change the
23
+         * password.
24
+         *
25
+         * @type {JitsiConference}
26
+         */
27
+        conference: React.PropTypes.object,
28
+
29
+        /**
30
+         * The value for how the conference is locked (or undefined if not
31
+         * locked) as defined by room-lock constants.
32
+         */
33
+        locked: React.PropTypes.string,
34
+
35
+        /**
36
+         * The current known password for the JitsiConference.
37
+         */
38
+        password: React.PropTypes.string,
39
+
40
+        /**
41
+         * Whether or not the password editing components should be displayed.
42
+         */
43
+        showPasswordEdit: React.PropTypes.bool,
44
+
45
+        /**
46
+         * Invoked to obtain translated strings.
47
+         */
48
+        t: React.PropTypes.func
49
+    }
50
+
51
+    /**
52
+     * Initializes a new PasswordContainer instance.
53
+     *
54
+     * @param {Object} props - The read-only properties with which the new
55
+     * instance is to be initialized.
56
+     */
57
+    constructor(props) {
58
+        super(props);
59
+
60
+        this.state = {
61
+            isEditingPassword: false
62
+        };
63
+
64
+        this._onTogglePasswordEdit = this._onTogglePasswordEdit.bind(this);
65
+    }
66
+
67
+    /**
68
+     * Implements React's {@link Component#render()}.
69
+     *
70
+     * @inheritdoc
71
+     * @returns {ReactElement}
72
+     */
73
+    render() {
74
+        return (
75
+            <div className = 'password-overview'>
76
+                <div className = 'password-overview-status'>
77
+                    <LockStatePanel locked = { Boolean(this.props.locked) } />
78
+                    { this._renderShowPasswordLink() }
79
+                </div>
80
+                { this._renderPasswordEdit() }
81
+            </div>
82
+        );
83
+    }
84
+
85
+    /**
86
+     * Toggles the display of the ReactElements used to edit the password.
87
+     *
88
+     * @private
89
+     * @returns {void}
90
+     */
91
+    _onTogglePasswordEdit() {
92
+        this.setState({
93
+            isEditingPassword: !this.state.isEditingPassword
94
+        });
95
+    }
96
+
97
+    /**
98
+     * Creates a ReactElement used for setting or removing a password.
99
+     *
100
+     * @private
101
+     * @returns {ReactElement|null}
102
+     */
103
+    _renderPasswordEdit() {
104
+        if (!this.state.isEditingPassword) {
105
+            return null;
106
+        }
107
+
108
+        return this.props.locked
109
+            ? <RemovePasswordForm
110
+                conference = { this.props.conference }
111
+                lockedLocally = { this.props.locked === LOCKED_LOCALLY }
112
+                password = { this.props.password } />
113
+            : <AddPasswordForm conference = { this.props.conference } />;
114
+    }
115
+
116
+    /**
117
+     * Creates a ReactElement that toggles displaying password edit components.
118
+     *
119
+     * @private
120
+     * @returns {ReactElement|null}
121
+     */
122
+    _renderShowPasswordLink() {
123
+        if (!this.props.showPasswordEdit) {
124
+            return null;
125
+        }
126
+
127
+        let toggleStatusKey;
128
+
129
+        if (this.state.isEditingPassword) {
130
+            toggleStatusKey = 'invite.hidePassword';
131
+        } else if (this.props.locked) {
132
+            toggleStatusKey = 'invite.showPassword';
133
+        } else {
134
+            toggleStatusKey = 'invite.addPassword';
135
+        }
136
+
137
+        return (
138
+            <a
139
+                className = 'password-overview-toggle-edit'
140
+                onClick = { this._onTogglePasswordEdit }>
141
+                { this.props.t(toggleStatusKey) }
142
+            </a>
143
+        );
144
+    }
145
+}
146
+
147
+export default translate(PasswordContainer);

+ 117
- 0
react/features/invite/components/RemovePasswordForm.js 파일 보기

@@ -0,0 +1,117 @@
1
+import React, { Component } from 'react';
2
+import { connect } from 'react-redux';
3
+
4
+import { setPassword } from '../../base/conference';
5
+import { translate } from '../../base/i18n';
6
+
7
+/**
8
+ * A React Component for removing a lock from a JitsiConference.
9
+ */
10
+class RemovePasswordForm extends Component {
11
+    /**
12
+     * RemovePasswordForm component's property types.
13
+     *
14
+     * @static
15
+     */
16
+    static propTypes = {
17
+        /**
18
+         * The JitsiConference on which remove a lock.
19
+         *
20
+         * @type {JitsiConference}
21
+         */
22
+        conference: React.PropTypes.object,
23
+
24
+        /**
25
+         * Invoked to send a password removal request.
26
+         */
27
+        dispatch: React.PropTypes.func,
28
+
29
+        /**
30
+         * Whether or not the room lock, if any, was set by the local user.
31
+         */
32
+        lockedLocally: React.PropTypes.bool,
33
+
34
+        /**
35
+         * The current known password for the JitsiConference.
36
+         */
37
+        password: React.PropTypes.string,
38
+
39
+        /**
40
+         * Invoked to obtain translated strings.
41
+         */
42
+        t: React.PropTypes.func
43
+    }
44
+
45
+    /**
46
+     * Initializes a new RemovePasswordForm instance.
47
+     *
48
+     * @param {Object} props - The read-only properties with which the new
49
+     * instance is to be initialized.
50
+     */
51
+    constructor(props) {
52
+        super(props);
53
+
54
+        this._onClick = this._onClick.bind(this);
55
+    }
56
+
57
+    /**
58
+     * Implements React's {@link Component#render()}.
59
+     *
60
+     * @private
61
+     * @returns {ReactElement}
62
+     */
63
+    render() {
64
+        return (
65
+            <div className = 'remove-password'>
66
+                <div className = 'remove-password-description'>
67
+                    { this._getPasswordPreviewText() }
68
+                </div>
69
+                <a
70
+                    className = 'remove-password-link'
71
+                    id = 'inviteDialogRemovePassword'
72
+                    onClick = { this._onClick }>
73
+                    { this.props.t('dialog.removePassword') }
74
+                </a>
75
+            </div>
76
+        );
77
+    }
78
+
79
+    /**
80
+     * Creates a ReactElement for displaying the current password.
81
+     *
82
+     * @private
83
+     * @returns {ReactElement}
84
+     */
85
+    _getPasswordPreviewText() {
86
+        return (
87
+            <span>
88
+                <span>
89
+                    { `${this.props.t('dialog.currentPassword')} ` }
90
+                </span>
91
+                <span className = 'remove-password-current'>
92
+                    { this.props.lockedLocally
93
+                        ? this.props.password
94
+                        : this.props.t('passwordSetRemotely') }
95
+                </span>
96
+            </span>
97
+        );
98
+    }
99
+
100
+    /**
101
+     * Dispatches a request to remove any set password on the JitsiConference.
102
+     *
103
+     * @private
104
+     * @returns {void}
105
+     */
106
+    _onClick() {
107
+        const conference = this.props.conference;
108
+
109
+        this.props.dispatch(setPassword(
110
+            conference,
111
+            conference.lock,
112
+            ''
113
+        ));
114
+    }
115
+}
116
+
117
+export default translate(connect()(RemovePasswordForm));

+ 110
- 0
react/features/invite/components/ShareLinkForm.js 파일 보기

@@ -0,0 +1,110 @@
1
+import React, { Component } from 'react';
2
+
3
+import { translate } from '../../base/i18n';
4
+
5
+const logger = require('jitsi-meet-logger').getLogger(__filename);
6
+
7
+/**
8
+ * A React Component for displaying a value with a copy button that can be
9
+ * clicked to copy the value onto the clipboard.
10
+ */
11
+class ShareLinkForm extends Component {
12
+    /**
13
+     * ShareLinkForm component's property types.
14
+     *
15
+     * @static
16
+     */
17
+    static propTypes = {
18
+        /**
19
+         * Invoked to obtain translated strings.
20
+         */
21
+        t: React.PropTypes.func,
22
+
23
+       /**
24
+         * The value to be displayed and copied onto the clipboard.
25
+         */
26
+        toCopy: React.PropTypes.string
27
+    }
28
+
29
+    /**
30
+     * Initializes a new ShareLinkForm instance.
31
+     *
32
+     * @param {Object} props - The read-only properties with which the new
33
+     * instance is to be initialized.
34
+     */
35
+    constructor(props) {
36
+        super(props);
37
+
38
+        this._inputElement = null;
39
+
40
+        this._onClick = this._onClick.bind(this);
41
+        this._setInput = this._setInput.bind(this);
42
+    }
43
+
44
+    /**
45
+     * Implements React's {@link Component#render()}.
46
+     *
47
+     * @inheritdoc
48
+     * @returns {ReactElement}
49
+     */
50
+    render() {
51
+        const inputValue = this.props.toCopy
52
+            || this.props.t('inviteUrlDefaultMsg');
53
+
54
+        // FIXME input is used here instead of atlaskit field-text because
55
+        // field-text does not currently support readonly
56
+        return (
57
+            <div className = 'form-control'>
58
+                <label className = 'form-control__label'>
59
+                    { this.props.t('dialog.shareLink') }
60
+                </label>
61
+                <div className = 'form-control__container'>
62
+                    <input
63
+                        className = 'input-control inviteLink'
64
+                        id = 'inviteLinkRef'
65
+                        readOnly = { true }
66
+                        ref = { this._setInput }
67
+                        type = 'text'
68
+                        value = { inputValue } />
69
+                    <button
70
+                        className =
71
+                            'button-control button-control_light copyInviteLink'
72
+                        onClick = { this._onClick }
73
+                        type = 'button'>
74
+                        { this.props.t('dialog.copy') }
75
+                    </button>
76
+                </div>
77
+            </div>
78
+        );
79
+    }
80
+
81
+    /**
82
+     * Copies the passed in value to the clipboard.
83
+     *
84
+     * @private
85
+     * @returns {void}
86
+     */
87
+    _onClick() {
88
+        try {
89
+            this._inputElement.select();
90
+            document.execCommand('copy');
91
+            this._inputElement.blur();
92
+        } catch (err) {
93
+            logger.error('error when copying the text', err);
94
+        }
95
+    }
96
+
97
+    /**
98
+     * Sets the internal reference to the DOM element for the input field so it
99
+     * may be accessed directly.
100
+     *
101
+     * @param {Object} element - DOM element for the component's input.
102
+     * @private
103
+     * @returns {void}
104
+     */
105
+    _setInput(element) {
106
+        this._inputElement = element;
107
+    }
108
+}
109
+
110
+export default translate(ShareLinkForm);

+ 1
- 0
react/features/invite/components/index.js 파일 보기

@@ -0,0 +1 @@
1
+export { default as InviteDialog } from './InviteDialog';

+ 2
- 0
react/features/invite/index.js 파일 보기

@@ -0,0 +1,2 @@
1
+export * from './actions';
2
+export * from './components';

+ 5
- 3
react/features/room-lock/components/PasswordRequiredPrompt.web.js 파일 보기

@@ -3,6 +3,8 @@ import React, { Component } from 'react';
3 3
 import { connect } from 'react-redux';
4 4
 import AKFieldText from '@atlaskit/field-text';
5 5
 
6
+import UIEvents from '../../../../service/UI/UIEvents';
7
+
6 8
 import { setPassword } from '../../base/conference';
7 9
 import { Dialog } from '../../base/dialog';
8 10
 import { translate } from '../../base/i18n';
@@ -109,9 +111,9 @@ class PasswordRequiredPrompt extends Component {
109 111
         // password required will be received and the room again
110 112
         // will be marked as locked.
111 113
         if (!this.state.password || this.state.password === '') {
112
-            // XXX temporary solution till we move the whole invite logic
113
-            // in react
114
-            APP.conference.invite.setLockedFromElsewhere(false);
114
+            // XXX temporary solution while some components are not listening
115
+            // for lock state updates in redux
116
+            APP.UI.emitEvent(UIEvents.TOGGLE_ROOM_LOCK, false);
115 117
         }
116 118
 
117 119
         this.props.dispatch(setPassword(

+ 12
- 0
react/features/room-lock/constants.js 파일 보기

@@ -0,0 +1,12 @@
1
+/**
2
+ * The room lock state where the password was set by the current user.
3
+ *
4
+ * @type {string}
5
+ */
6
+export const LOCKED_LOCALLY = 'LOCKED_LOCALLY';
7
+
8
+/**
9
+ * The room lock state where the password was set by a remote user.
10
+ * @type {string}
11
+ */
12
+export const LOCKED_REMOTELY = 'LOCKED_REMOTELY';

+ 1
- 0
react/features/room-lock/index.js 파일 보기

@@ -1,4 +1,5 @@
1 1
 export * from './actions';
2 2
 export * from './components';
3
+export * from './constants';
3 4
 
4 5
 import './middleware';

+ 56
- 5
react/features/room-lock/middleware.js 파일 보기

@@ -1,8 +1,16 @@
1 1
 /* global APP */
2
-import JitsiMeetJS from '../base/lib-jitsi-meet';
2
+const logger = require('jitsi-meet-logger').getLogger(__filename);
3
+
4
+import UIEvents from '../../../service/UI/UIEvents';
3 5
 
4
-import { CONFERENCE_FAILED } from '../base/conference';
6
+import {
7
+    CONFERENCE_FAILED,
8
+    LOCK_STATE_CHANGED,
9
+    SET_PASSWORD_FAILED
10
+} from '../base/conference';
11
+import JitsiMeetJS from '../base/lib-jitsi-meet';
5 12
 import { MiddlewareRegistry } from '../base/redux';
13
+
6 14
 import { _showPasswordDialog } from './actions';
7 15
 
8 16
 /**
@@ -20,17 +28,60 @@ MiddlewareRegistry.register(store => next => action => {
20 28
 
21 29
         if (action.conference
22 30
             && JitsiConferenceErrors.PASSWORD_REQUIRED === action.error) {
23
-            // XXX temporary solution till we move the whole invite
24
-            // logic in react
31
+            // XXX temporary solution while some components are not listening
32
+            // for lock state updates in redux
25 33
             if (typeof APP !== 'undefined') {
26
-                APP.conference.invite.setLockedFromElsewhere(true);
34
+                APP.UI.emitEvent(UIEvents.TOGGLE_ROOM_LOCK, true);
27 35
             }
28 36
 
29 37
             store.dispatch(_showPasswordDialog(action.conference));
30 38
         }
31 39
         break;
32 40
     }
41
+    case LOCK_STATE_CHANGED: {
42
+        // TODO Remove this logic when all components interested in the lock
43
+        // state change event are moved into react/redux.
44
+        if (typeof APP !== 'undefined') {
45
+            APP.UI.emitEvent(UIEvents.TOGGLE_ROOM_LOCK, action.locked);
46
+        }
47
+
48
+        break;
49
+    }
50
+    case SET_PASSWORD_FAILED:
51
+        return _notifySetPasswordError(store, next, action);
33 52
     }
34 53
 
35 54
     return next(action);
36 55
 });
56
+
57
+/**
58
+ * Handles errors that occur when a password is failed to be set.
59
+ *
60
+ * @param {Store} store - The Redux store in which the specified action is being
61
+ * dispatched.
62
+ * @param {Dispatch} next - The Redux dispatch function to dispatch the
63
+ * specified action to the specified store.
64
+ * @param {Action} action - The Redux action SET_PASSWORD_ERROR which has the
65
+ * error type that should be handled.
66
+ * @private
67
+ * @returns {Object} The new state that is the result of the reduction of the
68
+ * specified action.
69
+ */
70
+function _notifySetPasswordError(store, next, action) {
71
+    if (typeof APP !== 'undefined') {
72
+        // TODO remove this logic when displaying of error messages on web is
73
+        // handled through react/redux
74
+        if (action.error
75
+            === JitsiMeetJS.errors.conference.PASSWORD_NOT_SUPPORTED) {
76
+            logger.warn('room passwords not supported');
77
+            APP.UI.messageHandler.showError(
78
+                'dialog.warning', 'dialog.passwordNotSupported');
79
+        } else {
80
+            logger.warn('setting password failed', action.error);
81
+            APP.UI.messageHandler.showError(
82
+                'dialog.lockTitle', 'dialog.lockMessage');
83
+        }
84
+    }
85
+
86
+    return next(action);
87
+}

+ 1
- 1
react/features/toolbox/components/Toolbox.native.js 파일 보기

@@ -322,7 +322,7 @@ function _mapStateToProps(state) {
322 322
          * @protected
323 323
          * @type {boolean}
324 324
          */
325
-        _locked: conference.locked
325
+        _locked: Boolean(conference.locked)
326 326
     };
327 327
 }
328 328
 

+ 0
- 1
react/features/toolbox/components/Toolbox.web.js 파일 보기

@@ -223,7 +223,6 @@ function _mapDispatchToProps(dispatch: Function): Object {
223 223
  * @returns {{
224 224
  *     _alwaysVisible: boolean,
225 225
  *     _audioMuted: boolean,
226
- *     _locked: boolean,
227 226
  *     _subjectSlideIn: boolean,
228 227
  *     _videoMuted: boolean
229 228
  * }}

+ 3
- 1
react/features/toolbox/defaultToolbarButtons.js 파일 보기

@@ -4,6 +4,8 @@ import React from 'react';
4 4
 
5 5
 import UIEvents from '../../../service/UI/UIEvents';
6 6
 
7
+import { openInviteDialog } from '../invite';
8
+
7 9
 declare var APP: Object;
8 10
 declare var config: Object;
9 11
 declare var JitsiMeetJS: Object;
@@ -222,7 +224,7 @@ export default {
222 224
         id: 'toolbar_button_link',
223 225
         onClick() {
224 226
             JitsiMeetJS.analytics.sendEvent('toolbar.invite.clicked');
225
-            APP.UI.emitEvent(UIEvents.INVITE_CLICKED);
227
+            APP.store.dispatch(openInviteDialog());
226 228
         },
227 229
         tooltipKey: 'toolbar.invite'
228 230
     },

+ 0
- 4
service/UI/UIEvents.js 파일 보기

@@ -22,10 +22,6 @@ export default {
22 22
     VIDEO_MUTED: "UI.video_muted",
23 23
     ETHERPAD_CLICKED: "UI.etherpad_clicked",
24 24
     SHARED_VIDEO_CLICKED: "UI.start_shared_video",
25
-    /**
26
-     * Indicates that an invite button has been clicked.
27
-     */
28
-    INVITE_CLICKED: "UI.invite_clicked",
29 25
     /**
30 26
      * Updates shared video with params: url, state, time(optional)
31 27
      * Where url is the video link, state is stop/start/pause and time is the

Loading…
취소
저장