Browse Source

Merge branch 'master' into dev-vj

Vijayakrishnan Krishnan 5 years ago
parent
commit
1cb942ac40

+ 5 - 1
app/Http/Controllers/AppSessionController.php

@@ -76,6 +76,10 @@ class AppSessionController extends Controller
 
 
         // dd($response);
         // dd($response);
 
 
+        if ($request->ajax()) {
+            return response()->json($response);
+        }
+
         if(!isset($response['success']) || !$response['success']){
         if(!isset($response['success']) || !$response['success']){
             $message = 'API error';
             $message = 'API error';
             if(isset($response['error'])) {
             if(isset($response['error'])) {
@@ -91,4 +95,4 @@ class AppSessionController extends Controller
         return redirect($request->input('_success'));
         return redirect($request->input('_success'));
 
 
     }
     }
-}
+}

+ 34 - 5
app/Http/Controllers/ClientController.php

@@ -3,14 +3,43 @@
 namespace App\Http\Controllers;
 namespace App\Http\Controllers;
 
 
 use Illuminate\Http\Request;
 use Illuminate\Http\Request;
-use Illuminate\Support\Facades\DB;
-use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Http;
+use App\HttpModels\ClientLobbyModel;
+
+use Cookie;
+
+use App\Models\Lobby;
 
 
 class ClientController extends Controller
 class ClientController extends Controller
 {
 {
 
 
 	// GET /care_months
 	// GET /care_months
-	public function entrance(Request $request) {
-		return view('client/index');
+	public function entrance(Request $request, Lobby $lobby) {
+        $sessionKey = Cookie::get('sessionKey');
+
+        $lobbyModel = new ClientLobbyModel($lobby);
+        $response = response()->view('client/index',compact('lobbyModel'),200);
+
+        if(!$sessionKey){
+            $loginUrl = env('BACKEND_URL', 'http://localhost:8080') . '/api/session/createStrangerSession';
+
+            $httpResponse = Http::asForm()->post($loginUrl)->json();
+
+            if(!$httpResponse['success']){
+                return back()->with("message", $httpResponse['message']);
+            }
+
+            $sessionKey = $httpResponse['data'];
+
+            $cookie = cookie()->forever('sessionKey', $sessionKey, '/');
+
+            // $response = new \Illuminate\Http\Response(view('client/index'));
+            $response->withCookie($cookie);
+            // return $response;
+        }
+        // else {
+        //     return view('client/index');
+        // }
+        return $response;
 	}
 	}
-}
+}

+ 15 - 0
app/HttpModels/ClientLobbyModel.php

@@ -0,0 +1,15 @@
+<?php
+namespace App\HttpModels;
+
+use App\Models\Lobby;
+
+class ClientLobbyModel {
+    public $uid;
+    public $name;
+
+    public function __construct(Lobby $lobby)
+    {
+        $this->uid = $lobby->uid;
+        $this->name = $lobby->name;
+    }
+}

+ 566 - 94
resources/js/components/pages/ClientEntrance.vue

@@ -1,136 +1,609 @@
 <template>
 <template>
     <div class="login-box">
     <div class="login-box">
         <div class="login-logo auth-branding text-center border-0">
         <div class="login-logo auth-branding text-center border-0">
-            <span class="brand-text font-weight-light text-white">Welcome to <b>Dr. Smith & Dr. Brown's Office</b>!</span>
+            <span class="brand-text font-weight-light text-white">
+                Welcome to
+                <!-- <b>Dr. Smith & Dr. Brown's Office</b>! -->
+                <b>{{ lobbyProp.name }}</b>
+            </span>
         </div>
         </div>
-            <v-stepper v-model="stepper">
-                <v-stepper-header>
-                    <v-stepper-step :complete="stepper > 1" step="1">Check In</v-stepper-step>
+        <v-stepper v-model="stepper">
+            <v-stepper-header>
+                <v-stepper-step :complete="stepper > 1" step="1">Check In</v-stepper-step>
 
 
-                    <v-divider></v-divider>
+                <v-divider></v-divider>
 
 
-                    <v-stepper-step :complete="stepper > 2" step="2">Test your webcam</v-stepper-step>
+                <v-stepper-step :complete="stepper > 2" step="2">Test your webcam</v-stepper-step>
 
 
-                    <v-divider></v-divider>
+                <v-divider></v-divider>
 
 
-                    <v-stepper-step step="3">Meet your Doctor</v-stepper-step>
-                </v-stepper-header>
+                <v-stepper-step step="3">Meet your Doctor</v-stepper-step>
+            </v-stepper-header>
 
 
-                <v-stepper-items>
-                    <v-stepper-content step="1">
-
-                        <v-card
-                        class="mb-12"
-                        color="grey lighten-1"
-                        height="200px"
-                        >
+            <v-stepper-items>
+                <v-stepper-content step="1">
+                    <v-card class="mb-12" color="grey lighten-1" height="200px">
                         <div class="checkin-form d-flex justify-content-center align-items-center">
                         <div class="checkin-form d-flex justify-content-center align-items-center">
-                            <form action="/post-to-api"
-                                method="post"
-                                enctype="multipart/form-data">
+                            <div>
+                                <!-- <input type="hidden" name="lobbyUid" value />
+                                <input type="hidden" name="_api" value="/api/meeting/createAsStrangerPerformer" />
+                                <input type="hidden" name="_success" value="/pro/login" />
+                                <input type="hidden" name="_return" value="/" />-->
 
 
-                                <input type="hidden" name="lobbyUid" value=""/>
-                                <input type="hidden" name="_api" value="/api/session/proRequestSmsTokenToLogIn">
-                                <input type="hidden" name="_success" value="/pro/login">
-                                <input type="hidden" name="_return" value="/">
-
-                                <div class="input-group mb-3">
-                                    <input type="text" name="name" class="form-control" placeholder="Full Name" required>
+                                <div class="row mb-3">
+                                    <div class="col">
+                                        <input type="text" name="strangerFirstName" class="form-control" placeholder="First Name" v-model="user.firstName" required />
+                                    </div>
+                                    <div class="col">
+                                        <input type="text" name="strangerLastName" class="form-control" placeholder="Last Name" v-model="user.lastName" required />
+                                    </div>
                                 </div>
                                 </div>
 
 
                                 <div class="input-group mb-3">
                                 <div class="input-group mb-3">
-                                    <input type="date" name="dob" class="form-control" placeholder="Date of Birth" required>
+                                    <input type="date" name="strangerDob" class="form-control" placeholder="Date of Birth" v-model="user.dateOfBirth" required />
                                 </div>
                                 </div>
-                            </form>
+                            </div>
                         </div>
                         </div>
-                        </v-card>
+                    </v-card>
 
 
-                        <v-btn
-                        color="primary"
-                        @click="checkIn"
-                        :loading="loading"
-                        >
-                        Continue
-                        </v-btn>
-
-                    </v-stepper-content>
-
-                    <v-stepper-content step="2">
-                        <p>
-                            Please make sure your webcam is working. Accept the access request to your hardware on the top left to continue.
-                            <br>
-                            Then, someone should be with you shortly.
-                        </p>
-
-                        <v-card
-                        class="mb-12"
-                        color="grey lighten-1"
-                        height="200px"
-                        ></v-card>
-
-                        <v-btn
-                        color="primary"
-                        @click="stepper = 3"
-                        :disabled="!cameraWorkingConfirmed"
-                        >
-                        Continue
-                        </v-btn>
-
-                        <v-btn text @click="stepper = 1">Cancel</v-btn>
-                    </v-stepper-content>
-
-                    <v-stepper-content step="3">
-                        <p>
-                            Great!
-                        </p>
-
-                        <v-card
-                        class="mb-12"
-                        color="grey lighten-1"
-                        height="200px"
-                        ></v-card>
-
-                        <v-btn
-                        color="primary"
-                        @click="stepper = 1"
+                    <v-btn color="primary" @click="checkIn" :loading="loading">Continue</v-btn>
+                </v-stepper-content>
+
+                <v-stepper-content step="2">
+                    <p>
+                        Please make sure your webcam is working. Accept the access request to your hardware on the top left to continue.
+                        <br />Then, someone should be with you shortly.
+                    </p>
+
+                    <v-card class="mb-12" color="grey lighten-1" height="50vh">
+                        <grid-layout
+                            :layout="videoGridPreview"
+                            :key="`preview_${uniqueId}`"
+                            :col-num="maxCols"
+                            :max-rows="maxRows"
+                            :row-height="rowHeight"
+                            :is-draggable="false"
+                            :is-resizable="false"
+                            :verticalCompact="true"
+                            :margin="[gridPadding, gridPadding]"
+                            :use-css-transforms="true"
                         >
                         >
-                        Continue
-                        </v-btn>
+                            <grid-item v-for="video in videoGridPreview" :key="video.id" :id="video.id" :x="video.x" :y="video.y" :w="video.w" :h="video.h" :i="video.i"></grid-item>
+                        </grid-layout>
+                    </v-card>
+
+                    <v-btn color="primary" @click="gotoStep3" :disabled="!cameraWorkingConfirmed">Continue</v-btn>
+
+                    <v-btn text @click="stepper = 1">Cancel</v-btn>
+                </v-stepper-content>
 
 
-                        <v-btn text @click="stepper = 2">Cancel</v-btn>
-                    </v-stepper-content>
-                </v-stepper-items>
-            </v-stepper>
+                <v-stepper-content step="3">
+                    <p>Great! Please wait a little, doctor should join shortly.</p>
+
+                    <v-card class="mb-12" color="grey lighten-1" height="50vh">
+                        <grid-layout
+                            :layout="videoGrid"
+                            :key="uniqueId"
+                            :col-num="maxCols"
+                            :max-rows="maxRows"
+                            :row-height="rowHeight"
+                            :is-draggable="false"
+                            :is-resizable="false"
+                            :verticalCompact="true"
+                            :margin="[gridPadding, gridPadding]"
+                            :use-css-transforms="true"
+                        >
+                            <grid-item v-for="video in videoGrid" :key="video.id" :id="video.id" :x="video.x" :y="video.y" :w="video.w" :h="video.h" :i="video.i"></grid-item>
+                        </grid-layout>
+                    </v-card>
+                </v-stepper-content>
+            </v-stepper-items>
+        </v-stepper>
+        <div style="display: none" ref="otContainer"></div>
     </div>
     </div>
 </template>
 </template>
 
 
 <script>
 <script>
+import { mapState } from "vuex";
+
+import VueGridLayout from "vue-grid-layout";
+
 export default {
 export default {
+    components: {
+        GridLayout: VueGridLayout.GridLayout,
+        GridItem: VueGridLayout.GridItem
+    },
     props: {
     props: {
-        lobby_uid: {
-            type: String,
+        lobbyProp: {
+            type: Object,
             required: true
             required: true
         }
         }
+        // ,
+        // lobby_uid: {
+        //     type: String,
+        //     required: true
+        // }
     },
     },
     data() {
     data() {
         return {
         return {
+            user: {
+                firstName: "",
+                lastName: "",
+                dateOfBirth: null
+            },
+            meetingUid: "",
             stepper: 1,
             stepper: 1,
             cameraWorkingConfirmed: false,
             cameraWorkingConfirmed: false,
-            loading: false
-        }
+            loading: false,
+            /* Copied */
+            readyForUse: false,
+            uniqueId: Math.floor(Math.random() * Math.floor(10000)),
+            counter: 1,
+            openTokSession: null,
+            publisher: null,
+            screenPublisher: null,
+            subscribers: [],
+            publisherReady: false,
+            sessionConnected: false,
+            accessDialogShown: false,
+            videos: [],
+            videoGrid: [],
+            videoGridPreview: [],
+            maxCols: 12,
+            maxRows: 4,
+            rowHeight: 240,
+            gridPadding: 8,
+            loadingInProgress: false
+        };
     },
     },
     methods: {
     methods: {
-        checkIn(){
-            //ajax logic
+        checkIn() {
+            this.loading = true;
+            $.ajax({
+                url: "/post-to-api",
+                method: "POST",
+                headers: {
+                    "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content")
+                },
+                dataType: "json",
+                data: {
+                    _api: "/api/meeting/createAsStrangerPerformer",
+                    lobbyUid: this.lobbyProp.uid,
+                    title: `${this.user.firstName} ${this.user.lastName} ${this.user.dateOfBirth}`,
+                    strangerFirstName: this.user.firstName,
+                    strangerLastName: this.user.lastName,
+                    strangerDob: this.user.dateOfBirth
+                },
+                success: data => {
+                    this.stepper = 2;
+                    this.meetingUid = data.data;
+                    this.$nextTick(this.initializePublisher);
+                },
+                error: jXhr => {},
+                complete: () => {
+                    this.loading = false;
+                }
+            });
+        },
+        initializePublisher() {
+            this.publisher = OT.initPublisher(
+                this.$refs.otContainer,
+                {
+                    insertMode: "append",
+                    width: "100%",
+                    height: "100%",
+                    resolution: "1280x720",
+                    frameRate: 30,
+                    name: `${this.user.firstName} ${this.user.lastName}`,
+                    style: {
+                        nameDisplayMode: "on",
+                        archiveStatusDisplayMode: "off"
+                    }
+                },
+                error => {
+                    if (error) {
+                        alert(error.message);
+                    } else {
+                        this.publisherReady = true;
+                        const cont = this.createVideoContainer(true);
+                        this.$nextTick(() => {
+                            cont.el = $(`#${cont.id}`)[0];
+                            cont.el.appendChild(this.publisher.element);
+                            cont.obj = this.publisher;
+                            this.$set(cont, "self", true);
+                            this.cameraWorkingConfirmed = true;
+                        });
+                    }
+                }
+            );
+            this.publisher.on({
+                accessDialogOpened: e => {
+                    this.accessDialogShown = true;
+                },
+                accessDialogClosed: e => {
+                    this.accessDialogShown = false;
+                }
+            });
+        },
+        gotoStep3() {
+            this.stepper = 3;
+            this.videoGridPreview = [];
+            this.adjustVideoContainers();
+            this.$nextTick(() => {
+                const cont = this.videos[0];
+                cont.el = $(`#${cont.id}`)[0];
+                cont.el.appendChild(cont.obj.element);
+                this.getToken();
+            });
+        },
+        /* Copied */
+        disconnect() {
+            if (!this.openTokSession) return;
+            this.openTokSession.disconnect();
+            this.openTokSession.off();
+            this.videos = [];
+            this.publisher.destroy();
+            this.publisher = null;
+            this.subscribers = [];
+            this.publisherReady = false;
+            this.$store.commit("setSessionConnectivityState", false);
+            this.openTokSession = null;
+            if (this.screenPublisher) {
+                this.screenPublisher.destroy();
+                this.screenPublisher = null;
+                this.$store.commit("setScreenShareState", null);
+            }
+        },
+        getToken() {
+            // if (this.meeting.scheduledDate && !this.meeting.startedAt && this.meeting.scheduledDate > new Date() && (this.user.type === "guest" || !confirm("Meeting not started. Start it now?")))
+            //     return;
+            $.ajax({
+                url: "/post-to-api",
+                method: "POST",
+                headers: {
+                    "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content")
+                },
+                data: {
+                    _api: "/api/meeting/getToken",
+                    uid: this.meetingUid
+                },
+                success: data => {
+                    // this.$refs.initialModal.hide();
+                    // if (!isAssociate) {
+                    //     const user = Object.assign({}, this.user);
+                    //     user.id = data.participantId;
+                    //     user.name = data.participantName;
+                    //     user.pin = data.participantPin;
+                    //     this.$store.commit("setUser", user);
+                    // }
+                    this.initializeOpenTok(data.data.apiKey, data.data.sessionId, data.data.token);
+                },
+                error: jXhr => {
+                    if (jXhr.responseJSON && jXhr.responseJSON.errorCode) {
+                        switch (jXhr.responseJSON.errorCode) {
+                            case "LM-1":
+                                alert("Meeting not started yet.");
+                                break;
+                            case "LM-2":
+                                alert("You was kicked from this meeting.");
+                                window.location = "/";
+                                break;
+                            case "LM-3":
+                                alert("Wrong password");
+                                this.$refs.initialModal.disableWaiting();
+                                break;
+                        }
+                    } else {
+                        // this.$refs.initialModal.disableWaiting();
+                        alert(jXhr.responseJSON.message);
+                    }
+                }
+            });
+        },
+        createVideoContainer(preview = false) {
+            let videoCont = {};
+            videoCont.id = `${this.uniqueId}_video_${this.counter++}`;
+
+            this.videos.push(videoCont);
+            this.adjustVideoContainers(preview);
+
+            return videoCont;
+        },
+        adjustVideoContainers(preview = false) {
+            let videoGrid;
+            if (preview) {
+                videoGrid = this.videoGridPreview = [];
+            } else {
+                videoGrid = this.videoGrid = [];
+            }
+
+            let windowHeight = window.innerHeight / 2;
+            this.rowHeight = (windowHeight - this.gridPadding * (this.maxRows + 1)) / this.maxRows;
+
+            let cols = Math.ceil(Math.sqrt(this.videos.length));
+            let rows = Math.ceil(this.videos.length / cols);
+
+            let elementsLastRow = this.videos.length % cols;
+            let lastNormalIndex = this.videos.length - elementsLastRow;
+
+            if (elementsLastRow) {
+                this.maxCols = cols * elementsLastRow;
+            } else {
+                this.maxCols = cols;
+            }
+
+            let colsPerElement = this.maxCols / cols;
+            let rowsPerElement = this.maxRows / rows;
+
+            let colsLastRow = Math.ceil(this.maxCols / elementsLastRow);
+
+            let cntX = 0;
+            let cntY = 0;
+
+            for (let [index, video] of this.videos.entries()) {
+                video.i = index;
+                video.x = cntX;
+                video.y = cntY;
+                video.h = rowsPerElement;
+                video.w = colsPerElement;
+                cntX += colsPerElement;
 
 
-            this.stepper = 2
+                if (cntX >= this.maxCols) {
+                    cntX = 0;
+                    cntY += rowsPerElement;
+                }
+
+                let videoTemp = Object.assign({}, video);
+                videoTemp.video = video;
+                videoGrid.push(videoTemp);
+            }
+
+            /* OLD IMPLEMENTATION */
+
+            /* for (let [index, video] of this.videos.entries()) {
+                video.i = index
+                if (index < lastNormalIndex) {
+                    video.x = cntX
+                    video.y = cntY
+                    video.h = rowsPerElement
+                    video.w = colsPerElement
+                    cntX += colsPerElement
+
+                    if (cntX >= this.maxCols) {
+                        cntX = 0
+                        cntY += rowsPerElement
+                    }
+                } else {
+                    video.x = cntX
+                    video.y = cntY
+                    video.h = rowsPerElement
+                    video.w = colsLastRow
+                    cntX += colsLastRow
+                }
+
+                let videoTemp = Object.assign({}, video)
+                this.videoGrid.push(videoTemp)
+            } */
+
+            /* let gridColumn, gridRows = ''
+
+            if(rows && cols){
+                gridColumn = `grid-template-columns: repeat(${cols}, minmax(320px, 1fr))`
+                gridRows = `grid-template-rows: repeat(${rows}, minmax(240px, 1fr))`
+            } */
+        },
+        initializeOpenTok(apiKey, sessionId, token) {
+            this.openTokSession = OT.initSession(apiKey, sessionId);
+
+            this.openTokSession.on({
+                sessionDisconnected: event => {
+                    if (event.reason === "forceDisconnected") {
+                        alert("You were kicked.");
+                        //TODO: Kicked
+                        // if (this.user.type === "associate") {
+                        //     this.disconnect();
+                        //     this.$store.dispatch("leaveMeeting");
+                        // } else {
+                        //     window.location = "/";
+                        // }
+                    }
+                }
+            });
+
+            // Create a publisher
+            // this.publisher = OT.initPublisher(
+            //     this.$refs.otContainer,
+            //     {
+            //         insertMode: "append",
+            //         width: "100%",
+            //         height: "100%",
+            //         resolution: "1280x720",
+            //         frameRate: 30,
+            //         name: this.user.name,
+            //         style: {
+            //             nameDisplayMode: "on",
+            //             archiveStatusDisplayMode: "off"
+            //         }
+            //     },
+            //     error => {
+            //         if (error) {
+            //             alert(error.message);
+            //         } else {
+            //             this.publisherReady = true;
+            //             const cont = this.createVideoContainer();
+            //             this.$nextTick(() => {
+            //                 cont.el = $(`#${cont.id}`)[0];
+            //                 cont.el.appendChild(this.publisher.element);
+            //                 cont.obj = this.publisher;
+            //                 this.$set(cont, "self", true);
+            //                 //cont.self = true
+            //             });
+            //         }
+            //     }
+            // );
+
+            // this.publisher.on({
+            //     accessDialogOpened: e => {
+            //         this.accessDialogShown = true;
+            //     },
+            //     accessDialogClosed: e => {
+            //         this.accessDialogShown = false;
+            //     }
+            // });
+
+            this.openTokSession.on("streamCreated", event => {
+                //console.log("stream Created event")
+                let container;
+                if (event.stream.videoType === "screen") {
+                    //TODO: This is screenshare
+                }
+                const subscriber = this.openTokSession.subscribe(
+                    event.stream,
+                    this.$refs.otContainer,
+                    {
+                        insertMode: "append",
+                        width: "100%",
+                        height: "100%",
+                        style: { nameDisplayMode: "on" }
+                    },
+                    error => {
+                        if (error) {
+                            alert(error.message);
+                        } else {
+                            const cont = this.createVideoContainer();
+                            this.$nextTick(() => {
+                                cont.el = $(`#${cont.id}`)[0];
+                                cont.el.appendChild(subscriber.element);
+                                cont.obj = subscriber;
+                                container = cont;
+                            });
+                        }
+                    }
+                );
+                subscriber.on({
+                    destroyed: e => {
+                        container.el.remove();
+                        const index = this.videos.findIndex(v => v.id == container.id);
+                        if (index >= 0) this.videos.splice(index, 1);
+                        this.adjustVideoContainers();
+                    }
+                });
+                this.subscribers.push(subscriber);
+            });
+
+            console.log(sessionId);
+            console.log(token);
+            console.log(apiKey);
+            this.openTokSession.connect(token, error => {
+                // If the connection is successful, publish to the session
+                if (error) {
+                    alert(error.message);
+                } else {
+                    this.sessionConnected = true;
+                }
+            });
+            this.loadingInProgress = false;
+        },
+        publishToSession() {
+            this.openTokSession.publish(this.publisher, error => {
+                if (error) {
+                    alert(error.message);
+                }
+            });
+        },
+        ready() {
+            this.$nextTick(function() {
+                this.readyForUse = true;
+                if (this.user.type === "associate") {
+                    if (!this.meeting.id || this.active_menu_item.template !== "room") return;
+                    this.getToken({}, true);
+                    return;
+                }
+
+                if (this.meeting.scheduledDate && !this.meeting.startedAt && this.meeting.scheduledDate > new Date()) {
+                    alert("Meeting not started.");
+                    return;
+                }
+                let participantId = this.user.id;
+                if (!participantId) {
+                    this.$refs.initialModal.show(this.meeting.passwordRequired);
+                    return;
+                }
+                this.getToken({
+                    participantId
+                });
+            });
         }
         }
+    },
+    watch: {
+        publisherReady(val) {
+            if (val && this.sessionConnected) this.publishToSession();
+        },
+        sessionConnected(val) {
+            if (val && this.publisherReady) this.publishToSession();
+        },
+        "user.is_active_and_visible"(val) {
+            if (!this.publisher || !this.publisher.publishVideo) return;
+            this.publisher.publishVideo(val);
+            this.publisher.publishAudio(val);
+        },
+        "meeting.id"(val) {
+            if (this.loadingInProgress) return;
+            this.loadingInProgress = true;
+            this.disconnect();
+
+            if (val) {
+                this.getToken({}, true);
+            } else {
+                this.loadingInProgress = false;
+            }
+        },
+        "active_menu_item.template"(val) {
+            if (this.loadingInProgress || val !== "room" || !this.meeting.id || this.openTokSession) return;
+            this.loadingInProgress = true;
+            this.getToken({}, true);
+        }
+    },
+    mounted() {
+        let self = this;
+
+        this.$eventBus.$on("meetingRejoin", () => {
+            this.loadingInProgress = true;
+            this.disconnect();
+            this.getToken({}, true);
+        });
+
+        let width = $(window).width();
+        let height = $(window).height();
+
+        window.addEventListener("resize", function() {
+            let new_width = $(window).width();
+            let new_height = $(window).height();
+
+            if (width !== new_width || height !== new_height) {
+                self.adjustVideoContainers();
+            }
+        });
+
+        this.$eventBus.$on("leaveMeeting", () => {
+            $.ajax({
+                url: "/associate/meeting/leave",
+                method: "POST",
+                headers: {
+                    "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content")
+                },
+                error: jXhr => {
+                    console.error(getSingleError(jXhr));
+                }
+            });
+            this.$store.dispatch("leaveMeeting");
+            this.disconnect();
+        });
     }
     }
-}
+};
 </script>
 </script>
 
 
 <style lang="scss" scoped>
 <style lang="scss" scoped>
-
 .login-box {
 .login-box {
     width: 100%;
     width: 100%;
 }
 }
@@ -142,5 +615,4 @@ export default {
         width: 75%;
         width: 75%;
     }
     }
 }
 }
-
 </style>
 </style>

+ 1 - 5
resources/js/components/pages/MeetingsAppRoot.vue

@@ -8,7 +8,7 @@
                 </v-expansion-panel-content>
                 </v-expansion-panel-content>
             </v-expansion-panel>
             </v-expansion-panel>
 
 
-            <v-expansion-panel>
+            <v-expansion-panel class="no-v-padding">
                 <v-expansion-panel-header>Meeting Room</v-expansion-panel-header>
                 <v-expansion-panel-header>Meeting Room</v-expansion-panel-header>
                 <v-expansion-panel-content>
                 <v-expansion-panel-content>
                     <meeting-room></meeting-room>
                     <meeting-room></meeting-room>
@@ -124,10 +124,6 @@ export default {
 </script>
 </script>
 
 
 <style>
 <style>
-.v-expansion-panel--active {
-    height: 100%;
-}
-
 .v-expansion-panel-content__wrap {
 .v-expansion-panel-content__wrap {
     padding: 24px 16px;
     padding: 24px 16px;
 }
 }

+ 111 - 89
resources/js/components/partials/LobbyList.vue

@@ -1,104 +1,126 @@
 <template>
 <template>
-  <div>
-    <v-card v-for="lobby in lobbies" flat :key="lobby.id" :loading="loading">
+    <div>
+        <v-card v-for="lobby in lobbies" flat :key="lobby.id" :loading="loading">
+            <div class="d-flex flex-row justify-content-between">
+                <div>
+                    <v-card-title>{{lobby.name}}</v-card-title>
 
 
-    <div class="d-flex flex-row justify-content-between">
-        <div>
-            <v-card-title>{{lobby.name}}</v-card-title>
+                    <v-card-text>
+                        <div>{{lobby.description || 'No description available.'}}</div>
+                    </v-card-text>
+                </div>
+            </div>
 
 
-            <v-card-text>
-                <div>{{lobby.description || 'No description available.'}}</div>
-            </v-card-text>
-        </div>
-    </div>
-
-    <v-divider class="mx-4"></v-divider>
+            <v-divider class="mx-4"></v-divider>
 
 
-    <v-list dense v-if="meetingListFiltered(lobby.meetings).length">
-        <v-container class="overflow-y-auto px-0 pt-0">
-            <v-subheader class="d-flex flex-row justify-content-between">
-                <span>Meetings ({{meetingListFiltered(lobby.meetings).length}})</span>
-            </v-subheader>
-        </v-container>
-        <v-container style="height: 210px" class="overflow-y-auto">
-            <v-row>
-                <v-list-item-group class="w-100" v-model="lobby.selected_meeting" color="primary">
-                    <v-list-item v-for="(item, i) in meetingListFiltered(lobby.meetings)" :key="i" :value="item">
-                        <v-list-item-icon>
-                            <v-icon v-text="'mdi-clock'"></v-icon>
-                        </v-list-item-icon>
-                        <v-list-item-content>
-                            <v-list-item-title v-text="item.name"></v-list-item-title>
-                            <v-tooltip top v-if="item.pros_online.length > 0">
-                                <template v-slot:activator="{ on }">
-                                    <v-list-item-subtitle
-                                        v-on="on"
-                                        v-text="item.pros_online.length > 0 ? `PROs Connected (${item.pros_online.length})` : 'No PROs Connected'"
-                                    ></v-list-item-subtitle>
-                                </template>
-                                <span v-for="pro in item.pros_online" :key="pro.UID">
-                                    {{pro.name}}
-                                    <br />
-                                </span>
-                            </v-tooltip>
-                            <v-list-item-subtitle v-else v-text="item.pros_online.length > 0 ? `PROs Connected (${item.pros_online.length})` : 'No PROs Connected'"></v-list-item-subtitle>
-                        </v-list-item-content>
-                        <v-list-item-action>
-                            <v-list-item-action-text :class="{'status':true, 'is-active':item.active_members.length > 0}" v-text="'Online: ' + item.active_members.length"></v-list-item-action-text>
-                        </v-list-item-action>
-                    </v-list-item>
-                </v-list-item-group>
-            </v-row>
-        </v-container>
-    </v-list>
+            <v-list dense v-if="meetingListFiltered(lobby.meetings).length">
+                <v-container class="overflow-y-auto px-0 pt-0">
+                    <v-subheader class="d-flex flex-row justify-content-between">
+                        <span>Meetings ({{meetingListFiltered(lobby.meetings).length}})</span>
+                    </v-subheader>
+                </v-container>
+                <v-container style="height: 210px" class="overflow-y-auto">
+                    <v-row>
+                        <v-list-item-group class="w-100" v-model="lobby.selected_meeting" color="primary">
+                            <v-list-item v-for="(item, i) in meetingListFiltered(lobby.meetings)" :key="i" :value="item">
+                                <v-list-item-icon>
+                                    <v-icon v-text="'mdi-clock'"></v-icon>
+                                </v-list-item-icon>
+                                <v-list-item-content>
+                                    <v-list-item-title v-text="item.name"></v-list-item-title>
+                                    <v-tooltip top v-if="item.pros_online.length > 0">
+                                        <template v-slot:activator="{ on }">
+                                            <v-list-item-subtitle v-on="on" v-text="item.pros_online.length > 0 ? `PROs Connected (${item.pros_online.length})` : 'No PROs Connected'"></v-list-item-subtitle>
+                                        </template>
+                                        <span v-for="pro in item.pros_online" :key="pro.UID">
+                                            {{pro.name}}
+                                            <br />
+                                        </span>
+                                    </v-tooltip>
+                                    <v-list-item-subtitle v-else v-text="item.pros_online.length > 0 ? `PROs Connected (${item.pros_online.length})` : 'No PROs Connected'"></v-list-item-subtitle>
+                                </v-list-item-content>
+                                <v-list-item-action>
+                                    <v-list-item-action-text :class="{'status':true, 'is-active':item.active_members.length > 0}" v-text="'Online: ' + item.active_members.length"></v-list-item-action-text>
+                                </v-list-item-action>
+                            </v-list-item>
+                        </v-list-item-group>
+                    </v-row>
+                </v-container>
+            </v-list>
 
 
-    <v-list v-else>
-        <v-subheader class="d-flex flex-row justify-content-between">
-            <span>Meetings ({{meetingListFiltered(lobby.meetings).length}})</span>
-        </v-subheader>
-        <v-container style="height: 210px" class="overflow-y-auto">
-            <v-row>
-                <v-list-item-group class="w-100 text-center" color="primary">
-                    <v-list-item>
-                        <v-list-item-content>
-                            <v-list-item-title muted v-text="'No Meetings found for this lobby.'"></v-list-item-title>
-                        </v-list-item-content>
-                    </v-list-item>
-                </v-list-item-group>
-            </v-row>
-        </v-container>
-    </v-list>
+            <v-list v-else>
+                <v-subheader class="d-flex flex-row justify-content-between">
+                    <span>Meetings ({{meetingListFiltered(lobby.meetings).length}})</span>
+                </v-subheader>
+                <v-container style="height: 210px" class="overflow-y-auto">
+                    <v-row>
+                        <v-list-item-group class="w-100 text-center" color="primary">
+                            <v-list-item>
+                                <v-list-item-content>
+                                    <v-list-item-title muted v-text="'No Meetings found for this lobby.'"></v-list-item-title>
+                                </v-list-item-content>
+                            </v-list-item>
+                        </v-list-item-group>
+                    </v-row>
+                </v-container>
+            </v-list>
 
 
-    <transition-expand>
-        <div v-if="meetingListFiltered(lobby.meetings).length">
-            <v-divider class="mx-4"></v-divider>
+            <transition-expand>
+                <div v-if="meetingListFiltered(lobby.meetings).length">
+                    <v-divider class="mx-4"></v-divider>
 
 
-            <v-card-actions>
-                <v-btn :disabled="lobby.selected_meeting !== 0 && !lobby.selected_meeting" color="deep-purple accent-4" text @click="joinMeeting(lobby)">Join</v-btn>
-                <v-btn :disabled="lobby.selected_meeting !== 0 && !lobby.selected_meeting" color="deep-purple accent-4" text @click="inviteToMeeting(lobby)">Invite</v-btn>
-            </v-card-actions>
-        </div>
-    </transition-expand>
-</v-card>
-  </div>
+                    <v-card-actions>
+                        <v-btn :disabled="lobby.selected_meeting !== 0 && !lobby.selected_meeting" color="deep-purple accent-4" text @click="joinMeeting(lobby)">Join</v-btn>
+                        <v-btn :disabled="lobby.selected_meeting !== 0 && !lobby.selected_meeting" color="deep-purple accent-4" text @click="inviteToMeeting(lobby)">Invite</v-btn>
+                    </v-card-actions>
+                </div>
+            </transition-expand>
+        </v-card>
+    </div>
 </template>
 </template>
 
 
 <script>
 <script>
 import { mapState } from "vuex";
 import { mapState } from "vuex";
 
 
 export default {
 export default {
-  data(){
-    return {
-      loading: false
-    }
-  },
-  computed: {
-    ...mapState(["lobbies", "user"]),
-  },
-  methods: {
-    meetingListFiltered(meetings) {
-      return meetings.filter((x) => x.pros_online.length == 0 || x.pros_online.findIndex((y) => y.uid == this.user.uid) !== -1)
+    data() {
+        return {
+            loading: false
+        };
+    },
+    computed: {
+        ...mapState(["lobbies", "user", "meeting"])
+    },
+    methods: {
+        meetingListFiltered(meetings) {
+            return meetings.filter(x => x.pros_online.length == 0 || x.pros_online.findIndex(y => y.uid == this.user.uid) !== -1);
+        },
+        joinMeeting(lobby) {
+            this.loading = true;
+            $.ajax({
+                url: "/post-to-api",
+                method: "POST",
+                headers: {
+                    "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content")
+                },
+                dataType: "json",
+                data: {
+                    _api: "/api/meeting/joinAsPro",
+                    uid: lobby.selected_meeting.uid
+                },
+                success: data => {
+                    if (this.meeting.uid === lobby.selected_meeting.uid) {
+                        this.$eventBus.$emit("meetingRejoin");
+                    } else {
+                        this.$store.commit("setMeeting", lobby.selected_meeting);
+                    }
+                },
+                error: jXhr => {},
+                complete: () => {
+                    this.loading = false;
+                }
+            });
+        }
     }
     }
-  }
-}
+};
 </script>
 </script>

+ 447 - 453
resources/js/components/partials/MeetingRoom.vue

@@ -1,38 +1,38 @@
 <template>
 <template>
-  <div class="h-100" v-if="meeting.passwordRequired ? meeting.passwordProvided : true">
-    <div v-if="accessDialogShown">Please allow access to camera and microphone</div>
-      <div class="h-100" ref="videos">
-          <transition-group tag="div" class="video-wrapper" name="tiles">
-              <grid-layout
-                  :layout="videoGrid"
-                  :key="uniqueId"
-                  :col-num="maxCols"
-                  :max-rows="maxRows"
-                  :row-height="rowHeight"
-                  :is-draggable="true"
-                  :is-resizable="true"
-                  :verticalCompact="true"
-                  :margin="[gridPadding, gridPadding]"
-                  :use-css-transforms="true"
-              >
-                  <grid-item v-for="(video, index) in videoGrid" :key="video.id" :id="video.id" :x="video.x" :y="video.y" :w="video.w" :h="video.h" :i="video.i">
-                      <v-btn :color="'#282e38'" fab large dark class="v-btn--kick" @click="kickParcitipant(video)" v-show="!video.video.self && user.type == 'associate'">
-                          <v-icon>mdi-account-remove</v-icon>
-                      </v-btn>
-                  </grid-item>
-              </grid-layout>
-          </transition-group>
-          <!-- <div v-for="(video, index) in videos" :id="video.id" :key="video.id" @click="switchToFullSize(video, index)" :class="{'selected-high': video.id == bigMembersCountSelectedId, 'selected-low': lowMembersCountSelectedId == video.id}" class="video-container">
-          </div>-->
-      </div>
-      <button class="unscale" @click="bigMembersCountSelectedId = null" v-show="bigMembersCountSelectedId !== null">
-          <i class="mdi mdi-close"></i>
-      </button>
-      <div style="display:none" ref="otContainer">
-          <!-- Container to catch OpenTok Elements before reattaching -->
-      </div>
-      <!-- <modal-questions ref="initialModal" @questionsAnswered="getToken" @ready="ready"></modal-questions> -->
-  </div>
+    <div class="h-100" v-if="meeting.passwordRequired ? meeting.passwordProvided : true">
+        <div v-if="accessDialogShown">Please allow access to camera and microphone</div>
+        <div class="h-100" ref="videos">
+            <transition-group tag="div" class="video-wrapper" name="tiles">
+                <grid-layout
+                    :layout="videoGrid"
+                    :key="uniqueId"
+                    :col-num="maxCols"
+                    :max-rows="maxRows"
+                    :row-height="rowHeight"
+                    :is-draggable="true"
+                    :is-resizable="true"
+                    :verticalCompact="true"
+                    :margin="[gridPadding, gridPadding]"
+                    :use-css-transforms="true"
+                >
+                    <grid-item v-for="(video, index) in videoGrid" :key="video.id" :id="video.id" :x="video.x" :y="video.y" :w="video.w" :h="video.h" :i="video.i">
+                        <v-btn :color="'#282e38'" fab large dark class="v-btn--kick" @click="kickParcitipant(video)" v-show="!video.video.self && user.type == 'associate'">
+                            <v-icon>mdi-account-remove</v-icon>
+                        </v-btn>
+                    </grid-item>
+                </grid-layout>
+            </transition-group>
+            <!-- <div v-for="(video, index) in videos" :id="video.id" :key="video.id" @click="switchToFullSize(video, index)" :class="{'selected-high': video.id == bigMembersCountSelectedId, 'selected-low': lowMembersCountSelectedId == video.id}" class="video-container">
+            </div>-->
+        </div>
+        <button class="unscale" @click="bigMembersCountSelectedId = null" v-show="bigMembersCountSelectedId !== null">
+            <i class="mdi mdi-close"></i>
+        </button>
+        <div style="display:none" ref="otContainer">
+            <!-- Container to catch OpenTok Elements before reattaching -->
+        </div>
+        <!-- <modal-questions ref="initialModal" @questionsAnswered="getToken" @ready="ready"></modal-questions> -->
+    </div>
 </template>
 </template>
 
 
 <script>
 <script>
@@ -41,157 +41,151 @@ import { mapState } from "vuex";
 import VueGridLayout from "vue-grid-layout";
 import VueGridLayout from "vue-grid-layout";
 
 
 export default {
 export default {
-  components: {
-    GridLayout: VueGridLayout.GridLayout,
-    GridItem: VueGridLayout.GridItem
-  },
-  data() {
-    return {
-        readyForUse: false,
-        uniqueId: Math.floor(Math.random() * Math.floor(10000)),
-        counter: 1,
-        openTokSession: null,
-        publisher: null,
-        screenPublisher: null,
-        subscribers: [],
-        publisherReady: false,
-        accessDialogShown: false,
-        videos: [],
-        videoGrid: [],
-        bigMembersCountSelectedId: null,
-        lowMembersCountSelectedId: null,
-        maxCols: 12,
-        maxRows: 4,
-        rowHeight: 240,
-        gridPadding: 8,
-        loadingInProgress: false
-    };
-  },
-  computed: {
-    ...mapState(["meeting", "user", "session"])
-  },
-  methods: {
-    disconnect() {
-        if (!this.openTokSession) return;
-        this.openTokSession.disconnect();
-        this.openTokSession.off();
-        this.videos = [];
-        this.publisher.destroy();
-        this.publisher = null;
-        this.subscribers = [];
-        this.publisherReady = false;
-        this.$store.commit("setSessionConnectivityState", false);
-        this.openTokSession = null;
-        if (this.screenPublisher) {
-            this.screenPublisher.destroy();
-            this.screenPublisher = null;
-            this.$store.commit("setScreenShareState", null);
-        }
+    components: {
+        GridLayout: VueGridLayout.GridLayout,
+        GridItem: VueGridLayout.GridItem
     },
     },
-    getToken(data, isAssociate) {
-        if (this.meeting.scheduledDate && !this.meeting.startedAt && this.meeting.scheduledDate > new Date() && (this.user.type === "guest" || !confirm("Meeting not started. Start it now?")))
-            return;
-        data.meetingId = this.meeting.id;
-        data.userUid = this.user.UID;
-        $.ajax({
-            url: isAssociate ? "/associate/token" : "/token",
-            method: "POST",
-            headers: {
-                "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content")
-            },
-            data,
-            success: data => {
-                this.$refs.initialModal.hide();
-                if (!isAssociate) {
-                    const user = Object.assign({}, this.user);
-                    user.id = data.participantId;
-                    user.name = data.participantName;
-                    user.pin = data.participantPin;
-                    this.$store.commit("setUser", user);
-                }
-                this.initializeOpenTok(data.apiKey, data.sessionId, data.token);
-            },
-            error: jXhr => {
-                if (jXhr.responseJSON && jXhr.responseJSON.errorCode) {
-                    switch (jXhr.responseJSON.errorCode) {
-                        case "LM-1":
-                            alert("Meeting not started yet.");
-                            break;
-                        case "LM-2":
-                            alert("You was kicked from this meeting.");
-                            window.location = "/";
-                            break;
-                        case "LM-3":
-                            alert("Wrong password");
-                            this.$refs.initialModal.disableWaiting();
-                            break;
+    data() {
+        return {
+            readyForUse: false,
+            uniqueId: Math.floor(Math.random() * Math.floor(10000)),
+            counter: 1,
+            openTokSession: null,
+            publisher: null,
+            screenPublisher: null,
+            subscribers: [],
+            publisherReady: false,
+            accessDialogShown: false,
+            videos: [],
+            videoGrid: [],
+            bigMembersCountSelectedId: null,
+            lowMembersCountSelectedId: null,
+            maxCols: 1,
+            maxRows: 12,
+            rowHeight: 120,
+            gridPadding: 0,
+            loadingInProgress: false
+        };
+    },
+    computed: {
+        ...mapState(["meeting", "user", "session"])
+    },
+    methods: {
+        disconnect() {
+            if (!this.openTokSession) return;
+            this.openTokSession.disconnect();
+            this.openTokSession.off();
+            this.videos = [];
+            this.publisher.destroy();
+            this.publisher = null;
+            this.subscribers = [];
+            this.publisherReady = false;
+            this.$store.commit("setSessionConnectivityState", false);
+            this.openTokSession = null;
+            if (this.screenPublisher) {
+                this.screenPublisher.destroy();
+                this.screenPublisher = null;
+                this.$store.commit("setScreenShareState", null);
+            }
+        },
+        getToken() {
+            $.ajax({
+                url: "/post-to-api",
+                method: "POST",
+                headers: {
+                    "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content")
+                },
+                data: {
+                    _api: "/api/meeting/getToken",
+                    uid: this.meeting.uid
+                },
+                success: data => {
+                    this.initializeOpenTok(data.data.apiKey, data.data.sessionId, data.data.token);
+                },
+                error: jXhr => {
+                    if (jXhr.responseJSON && jXhr.responseJSON.errorCode) {
+                        switch (jXhr.responseJSON.errorCode) {
+                            case "LM-1":
+                                alert("Meeting not started yet.");
+                                break;
+                            case "LM-2":
+                                alert("You was kicked from this meeting.");
+                                window.location = "/";
+                                break;
+                            case "LM-3":
+                                alert("Wrong password");
+                                this.$refs.initialModal.disableWaiting();
+                                break;
+                        }
+                    } else {
+                        this.$refs.initialModal.disableWaiting();
+                        alert(getSingleError(jXhr));
                     }
                     }
-                } else {
-                    this.$refs.initialModal.disableWaiting();
-                    alert(getSingleError(jXhr));
                 }
                 }
+            });
+        },
+        createVideoContainer() {
+            let videoCont = {};
+            videoCont.id = `${this.uniqueId}_video_${this.counter++}`;
+            if (this.videos.length == 1 && !this.lowMembersCountSelectedId) {
+                this.lowMembersCountSelectedId = videoCont.id;
             }
             }
-        });
-    },
-    createVideoContainer() {
-        let videoCont = {};
-        videoCont.id = `${this.uniqueId}_video_${this.counter++}`;
-        if (this.videos.length == 1 && !this.lowMembersCountSelectedId) {
-            this.lowMembersCountSelectedId = videoCont.id;
-        }
 
 
-        this.videos.push(videoCont);
-        this.adjustVideoContainers();
+            this.videos.push(videoCont);
+            this.adjustVideoContainers();
 
 
-        return videoCont;
-    },
-    adjustVideoContainers() {
-        this.videoGrid = [];
+            return videoCont;
+        },
+        adjustVideoContainers() {
+            this.videoGrid = [];
 
 
-        let windowHeight = window.innerHeight;
-        this.rowHeight = (windowHeight - this.gridPadding * (this.maxRows + 1)) / this.maxRows;
+            let windowHeight = window.innerHeight * 0.75;
+            this.rowHeight = (windowHeight - this.gridPadding * (this.maxRows + 1)) / this.maxRows;
 
 
-        let cols = Math.ceil(Math.sqrt(this.videos.length));
-        let rows = Math.ceil(this.videos.length / cols);
+            //let cols = Math.ceil(Math.sqrt(this.videos.length));
+            let cols = 1
+            let rows = Math.ceil(this.videos.length / cols)
 
 
-        let elementsLastRow = this.videos.length % cols;
-        let lastNormalIndex = this.videos.length - elementsLastRow;
+            let elementsLastRow = this.videos.length % cols
+            let lastNormalIndex = this.videos.length - elementsLastRow
 
 
-        if (elementsLastRow) {
-            this.maxCols = cols * elementsLastRow;
-        } else {
-            this.maxCols = cols;
-        }
+            if (elementsLastRow) {
+                this.maxCols = cols * elementsLastRow
+            } else {
+                this.maxCols = cols
+            }
 
 
-        let colsPerElement = this.maxCols / cols;
-        let rowsPerElement = this.maxRows / rows;
+            //let colsPerElement = this.maxCols / cols;
+            let rowsPerElement = this.maxRows / rows
 
 
-        let colsLastRow = Math.ceil(this.maxCols / elementsLastRow);
+            let colsPerElement = 1
 
 
-        let cntX = 0;
-        let cntY = 0;
+            let colsLastRow = Math.ceil(this.maxCols / elementsLastRow)
 
 
-        for (let [index, video] of this.videos.entries()) {
-            video.i = index;
-            video.x = cntX;
-            video.y = cntY;
-            video.h = rowsPerElement;
-            video.w = colsPerElement;
-            cntX += colsPerElement;
-
-            if (cntX >= this.maxCols) {
-                cntX = 0;
-                cntY += rowsPerElement;
-            }
+            let cntX = 0
+            let cntY = 0
 
 
-            let videoTemp = Object.assign({}, video);
-            videoTemp.video = video;
-            this.videoGrid.push(videoTemp);
-        }
+            for (let [index, video] of this.videos.entries()) {
+                video.i = index;
+                video.x = cntX;
+                video.y = cntY;
+                video.h = rowsPerElement;
+                video.w = colsPerElement;
+                cntX += colsPerElement;
+
+                if (cntX >= this.maxCols) {
+                    cntX = 0;
+                    cntY += rowsPerElement;
+                }
+
+                let videoTemp = Object.assign({}, video);
+                videoTemp.video = video;
+                this.videoGrid.push(videoTemp);
+            }
 
 
-        /* OLD IMPLEMENTATION */
+            /* OLD IMPLEMENTATION */
 
 
-        /* for (let [index, video] of this.videos.entries()) {
+            /* for (let [index, video] of this.videos.entries()) {
             video.i = index;
             video.i = index;
             if (index < lastNormalIndex) {
             if (index < lastNormalIndex) {
                 video.x = cntX;
                 video.x = cntX;
@@ -216,308 +210,308 @@ export default {
             this.videoGrid.push(videoTemp);
             this.videoGrid.push(videoTemp);
         } */
         } */
 
 
-        /* let gridColumn, gridRows = ''
+            /* let gridColumn, gridRows = ''
 
 
         if(rows && cols){
         if(rows && cols){
             gridColumn = `grid-template-columns: repeat(${cols}, minmax(320px, 1fr));`
             gridColumn = `grid-template-columns: repeat(${cols}, minmax(320px, 1fr));`
             gridRows = `grid-template-rows: repeat(${rows}, minmax(240px, 1fr));`
             gridRows = `grid-template-rows: repeat(${rows}, minmax(240px, 1fr));`
         } */
         } */
-    },
-    initializeOpenTok(apiKey, sessionId, token) {
-      this.openTokSession = OT.initSession(apiKey, sessionId);
-
-      this.openTokSession.on({
-          sessionDisconnected: event => {
-              if (event.reason === "forceDisconnected") {
-                  alert("You were kicked.");
-                  if (this.user.type === "associate") {
-                      this.disconnect();
-                      this.$store.dispatch("leaveMeeting");
-                  } else {
-                      window.location = "/";
-                  }
-              }
-          }
-      });
-
-      // Create a publisher
-      this.publisher = OT.initPublisher(
-          this.$refs.otContainer,
-          {
-            insertMode: "append",
-            width: "100%",
-            height: "100%",
-            resolution: "1280x720",
-            frameRate: 30,
-            name: this.user.name,
-            style: {
-                nameDisplayMode: "on",
-                archiveStatusDisplayMode: "off"
-            }
-          },
-          error => {
-            if (error) {
-                alert(error.message);
-            } else {
-                this.publisherReady = true;
-                const cont = this.createVideoContainer();
-                this.$nextTick(() => {
-                    cont.el = $(`#${cont.id}`)[0];
-                    cont.el.appendChild(this.publisher.element);
-                    cont.obj = this.publisher;
-                    this.$set(cont, "self", true);
-                    //cont.self = true;
+        },
+        initializeOpenTok(apiKey, sessionId, token) {
+            this.openTokSession = OT.initSession(apiKey, sessionId);
+
+            this.openTokSession.on({
+                sessionDisconnected: event => {
+                    if (event.reason === "forceDisconnected") {
+                        alert("You were kicked.");
+                        if (this.user.type === "associate") {
+                            this.disconnect();
+                            this.$store.dispatch("leaveMeeting");
+                        } else {
+                            window.location = "/";
+                        }
+                    }
+                }
+            });
+
+            // Create a publisher
+            this.publisher = OT.initPublisher(
+                this.$refs.otContainer,
+                {
+                    insertMode: "append",
+                    width: "100%",
+                    height: "100%",
+                    resolution: "1280x720",
+                    frameRate: 30,
+                    name: this.user.name,
+                    style: {
+                        nameDisplayMode: "on",
+                        archiveStatusDisplayMode: "off"
+                    }
+                },
+                error => {
+                    if (error) {
+                        alert(error.message);
+                    } else {
+                        this.publisherReady = true;
+                        const cont = this.createVideoContainer();
+                        this.$nextTick(() => {
+                            cont.el = $(`#${cont.id}`)[0];
+                            cont.el.appendChild(this.publisher.element);
+                            cont.obj = this.publisher;
+                            this.$set(cont, "self", true);
+                            //cont.self = true;
+                        });
+                    }
+                }
+            );
+
+            this.publisher.on({
+                accessDialogOpened: e => {
+                    this.accessDialogShown = true;
+                },
+                accessDialogClosed: e => {
+                    this.accessDialogShown = false;
+                }
+            });
+
+            this.openTokSession.on("streamCreated", event => {
+                //console.log("stream Created event");
+                let container;
+                if (event.stream.videoType === "screen") {
+                    //TODO: This is
+                }
+                const subscriber = this.openTokSession.subscribe(
+                    event.stream,
+                    this.$refs.otContainer,
+                    {
+                        insertMode: "append",
+                        width: "100%",
+                        height: "100%",
+                        style: { nameDisplayMode: "on" }
+                    },
+                    error => {
+                        if (error) {
+                            alert(error.message);
+                        } else {
+                            const cont = this.createVideoContainer();
+                            this.$nextTick(() => {
+                                cont.el = $(`#${cont.id}`)[0];
+                                cont.el.appendChild(subscriber.element);
+                                cont.obj = subscriber;
+                                container = cont;
+                            });
+                        }
+                    }
+                );
+                subscriber.on({
+                    destroyed: e => {
+                        container.el.remove();
+                        const index = this.videos.findIndex(v => v.id == container.id);
+                        if (index >= 0) this.videos.splice(index, 1);
+                        this.adjustVideoContainers();
+                    }
                 });
                 });
+                this.subscribers.push(subscriber);
+            });
+
+            this.openTokSession.connect(token, error => {
+                // If the connection is successful, publish to the session
+                if (error) {
+                    alert(error.message);
+                } else {
+                    this.$store.commit("setSessionConnectivityState", true);
+                }
+            });
+            this.loadingInProgress = false;
+        },
+        publishToSession() {
+            this.openTokSession.publish(this.publisher, error => {
+                if (error) {
+                    alert(error.message);
+                }
+            });
+        },
+        switchToFullSize(video, index) {
+            if (this.videos.length < 4) {
+                if (this.lowMembersCountSelectedId !== video.id && index !== 0) {
+                    this.lowMembersCountSelectedId = video.id;
+                }
+            } else {
+                this.bigMembersCountSelectedId = video.id;
             }
             }
-          }
-      );
-
-      this.publisher.on({
-          accessDialogOpened: e => {
-            this.accessDialogShown = true;
-          },
-          accessDialogClosed: e => {
-            this.accessDialogShown = false;
-          }
-      });
-
-      this.openTokSession.on("streamCreated", event => {
-        //console.log("stream Created event");
-        let container;
-        if (event.stream.videoType === "screen") {
-          //TODO: This is
+        },
+        ready() {
+            this.$nextTick(function() {
+                this.readyForUse = true;
+                if (this.user.type === "associate") {
+                    if (!this.meeting.id || this.active_menu_item.template !== "room") return;
+                    this.getToken({}, true);
+                    return;
+                }
+
+                if (this.meeting.scheduledDate && !this.meeting.startedAt && this.meeting.scheduledDate > new Date()) {
+                    alert("Meeting not started.");
+                    return;
+                }
+                let participantId = this.user.id;
+                if (!participantId) {
+                    this.$refs.initialModal.show(this.meeting.passwordRequired);
+                    return;
+                }
+                this.getToken({
+                    participantId
+                });
+            });
+        },
+        shareScreen() {
+            if (this.screenPublisher) return;
+            this.screenPublisher = OT.initPublisher(
+                this.$refs.otContainer,
+                {
+                    insertMode: "append",
+                    width: "100%",
+                    height: "100%",
+                    resolution: "1280x720",
+                    frameRate: 30,
+                    videoSource: "screen",
+                    name: this.user.name,
+                    style: {
+                        nameDisplayMode: "on",
+                        archiveStatusDisplayMode: "off"
+                    }
+                },
+                error => {
+                    if (error) {
+                        console.error(error);
+                        if (error.code !== 1500) alert(error.message);
+                        this.screenPublisher = null;
+                    } else {
+                        const cont = this.createVideoContainer();
+                        this.$nextTick(() => {
+                            cont.el = $(`#${cont.id}`)[0];
+                            cont.el.appendChild(this.screenPublisher.element);
+                            cont.obj = this.screenPublisher;
+                            // cont.self = true;
+
+                            this.$set(cont, "self", true);
+                        });
+                        this.screenPublisher.on("streamDestroyed", event => {
+                            cont.el.remove();
+                            const index = this.videos.findIndex(v => v.id == cont.id);
+                            if (index >= 0) this.videos.splice(index, 1);
+                            this.adjustVideoContainers();
+                            this.screenPublisher = null;
+                            this.$store.commit("setScreenShareState", false);
+                        });
+                        this.openTokSession.publish(this.screenPublisher, error => {
+                            if (error) {
+                                alert(error.message);
+                            }
+                        });
+                        this.$store.commit("setScreenShareState", true);
+                    }
+                }
+            );
+        },
+        kickParcitipantContinue(participant) {
+            const connection = participant.video.obj.stream.connection;
+            const [type, id] = connection.data.split(":");
+            this.openTokSession.forceDisconnect(connection, error => {
+                if (error) {
+                    console.error(error);
+                } else {
+                    $.ajax({
+                        url: `/associate/meeting/${this.meeting.id}/kick`,
+                        method: "POST",
+                        headers: {
+                            "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content")
+                        },
+                        data: {
+                            type,
+                            id
+                        },
+                        error: jXhr => {
+                            console.error(getSingleError(jXhr));
+                        }
+                    });
+                }
+            });
+        },
+        kickParcitipant(participant) {
+            if (confirm("Are you sure want to kick this person out of meeting?")) this.kickParcitipantContinue(participant);
         }
         }
-        const subscriber = this.openTokSession.subscribe(
-          event.stream,
-          this.$refs.otContainer,
-          {
-            insertMode: "append",
-            width: "100%",
-            height: "100%",
-            style: { nameDisplayMode: "on" }
-          },
-          error => {
-              if (error) {
-                alert(error.message);
-              } else {
-                  const cont = this.createVideoContainer();
-                  this.$nextTick(() => {
-                    cont.el = $(`#${cont.id}`)[0];
-                    cont.el.appendChild(subscriber.element);
-                    cont.obj = subscriber;
-                    container = cont;
-                  });
-              }
-          }
-        );
-        subscriber.on({
-          destroyed: e => {
-            container.el.remove();
-            const index = this.videos.findIndex(v => v.id == container.id);
-            if (index >= 0) this.videos.splice(index, 1);
-            this.adjustVideoContainers();
-          }
-        });
-        this.subscribers.push(subscriber);
-      });
-
-    this.openTokSession.connect(token, error => {
-        // If the connection is successful, publish to the session
-        if (error) {
-            alert(error.message);
-        } else {
-            this.$store.commit("setSessionConnectivityState", true);
+    },
+    watch: {
+        publisherReady(val) {
+            if (val && this.session.sessionConnected) this.publishToSession();
+        },
+        "session.sessionConnected"(val) {
+            if (val && this.publisherReady) this.publishToSession();
+        },
+        "user.is_active_and_visible"(val) {
+            if (!this.publisher || !this.publisher.publishVideo) return;
+            this.publisher.publishVideo(val);
+            this.publisher.publishAudio(val);
+        },
+        "meeting.uid"(val) {
+            if (this.loadingInProgress) return;
+            this.loadingInProgress = true;
+            this.disconnect();
+
+            if (val) {
+                this.getToken();
+            } else {
+                this.loadingInProgress = false;
+            }
+        },
+        "active_menu_item.template"(val) {
+            if (this.loadingInProgress || val !== "room" || !this.meeting.id || this.openTokSession) return;
+            this.loadingInProgress = true;
+            this.getToken({}, true);
         }
         }
-      });
-      this.loadingInProgress = false;
     },
     },
-    publishToSession() {
-        this.openTokSession.publish(this.publisher, error => {
-          if (error) {
-            alert(error.message);
-          }
+    created() {
+        OT.checkScreenSharingCapability(response => {
+            this.$store.commit("setScreesharingAvailability", response.supported);
         });
         });
     },
     },
-    switchToFullSize(video, index) {
-        if (this.videos.length < 4) {
-          if (this.lowMembersCountSelectedId !== video.id && index !== 0) {
-            this.lowMembersCountSelectedId = video.id;
-          }
-        } else {
-          this.bigMembersCountSelectedId = video.id;
-        }
-    },
-    ready() {
-        this.$nextTick(function() {
-          this.readyForUse = true;
-          if (this.user.type === "associate") {
-            if (!this.meeting.id || this.active_menu_item.template !== "room") return;
+    mounted() {
+        let self = this;
+
+        this.$eventBus.$on("screenShare", this.shareScreen);
+        this.$eventBus.$on("meetingRejoin", () => {
+            this.loadingInProgress = true;
+            this.disconnect();
             this.getToken({}, true);
             this.getToken({}, true);
-            return;
-          }
-
-          if (this.meeting.scheduledDate && !this.meeting.startedAt && this.meeting.scheduledDate > new Date()) {
-            alert("Meeting not started.");
-            return;
-          }
-          let participantId = this.user.id;
-          if (!participantId) {
-            this.$refs.initialModal.show(this.meeting.passwordRequired);
-            return;
-          }
-          this.getToken({
-            participantId
-          });
         });
         });
-    },
-    shareScreen() {
-        if (this.screenPublisher) return;
-        this.screenPublisher = OT.initPublisher(
-          this.$refs.otContainer,
-          {
-            insertMode: "append",
-            width: "100%",
-            height: "100%",
-            resolution: "1280x720",
-            frameRate: 30,
-            videoSource: "screen",
-            name: this.user.name,
-            style: {
-              nameDisplayMode: "on",
-              archiveStatusDisplayMode: "off"
+
+        let width = $(window).width();
+        let height = $(window).height();
+
+        window.addEventListener("resize", function() {
+            let new_width = $(window).width();
+            let new_height = $(window).height();
+
+            if (width !== new_width || height !== new_height) {
+                self.adjustVideoContainers();
             }
             }
-          },
-          error => {
-              if (error) {
-                console.error(error);
-                if (error.code !== 1500) alert(error.message);
-                this.screenPublisher = null;
-              } else {
-                const cont = this.createVideoContainer();
-                this.$nextTick(() => {
-                  cont.el = $(`#${cont.id}`)[0];
-                  cont.el.appendChild(this.screenPublisher.element);
-                  cont.obj = this.screenPublisher;
-                  // cont.self = true;
-
-                  this.$set(cont, "self", true);
-                });
-                this.screenPublisher.on("streamDestroyed", event => {
-                  cont.el.remove();
-                  const index = this.videos.findIndex(v => v.id == cont.id);
-                  if (index >= 0) this.videos.splice(index, 1);
-                  this.adjustVideoContainers();
-                  this.screenPublisher = null;
-                  this.$store.commit("setScreenShareState", false);
-                });
-                this.openTokSession.publish(this.screenPublisher, error => {
-                  if (error) {
-                    alert(error.message);
-                  }
-                });
-                this.$store.commit("setScreenShareState", true);
-              }
-          }
-        );
-    },
-    kickParcitipantContinue(participant) {
-        const connection = participant.video.obj.stream.connection;
-        const [type, id] = connection.data.split(":");
-        this.openTokSession.forceDisconnect(connection, error => {
-          if (error) {
-            console.error(error);
-          } else {
+        });
+
+        this.$eventBus.$on("leaveMeeting", () => {
             $.ajax({
             $.ajax({
-              url: `/associate/meeting/${this.meeting.id}/kick`,
-              method: "POST",
-              headers: {
-                "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content")
-              },
-              data: {
-                type,
-                id
-              },
-              error: jXhr => {
-                console.error(getSingleError(jXhr));
-              }
+                url: "/associate/meeting/leave",
+                method: "POST",
+                headers: {
+                    "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content")
+                },
+                error: jXhr => {
+                    console.error(getSingleError(jXhr));
+                }
             });
             });
-          }
+            this.$store.dispatch("leaveMeeting");
+            this.disconnect();
         });
         });
-    },
-    kickParcitipant(participant) {
-      if (confirm("Are you sure want to kick this person out of meeting?")) this.kickParcitipantContinue(participant);
     }
     }
-},
-watch: {
-    publisherReady(val) {
-      if (val && this.session.sessionConnected) this.publishToSession();
-    },
-    "session.sessionConnected"(val) {
-      if (val && this.publisherReady) this.publishToSession();
-    },
-    "user.is_active_and_visible"(val) {
-      if (!this.publisher || !this.publisher.publishVideo) return;
-      this.publisher.publishVideo(val);
-      this.publisher.publishAudio(val);
-    },
-    "meeting.id"(val) {
-      if (this.loadingInProgress) return;
-      this.loadingInProgress = true;
-      this.disconnect();
-
-      if (val) {
-          this.getToken({}, true);
-      } else {
-          this.loadingInProgress = false;
-      }
-    },
-    "active_menu_item.template"(val) {
-      if (this.loadingInProgress || val !== "room" || !this.meeting.id || this.openTokSession) return;
-      this.loadingInProgress = true;
-      this.getToken({}, true);
-    }
-  },
-  created() {
-    OT.checkScreenSharingCapability(response => {
-      this.$store.commit("setScreesharingAvailability", response.supported);
-    });
-  },
-  mounted() {
-    let self = this;
-
-    this.$eventBus.$on("screenShare", this.shareScreen);
-    this.$eventBus.$on("meetingRejoin", () => {
-      this.loadingInProgress = true;
-      this.disconnect();
-      this.getToken({}, true);
-    });
-
-    let width = $(window).width();
-    let height = $(window).height();
-
-    window.addEventListener("resize", function() {
-      let new_width = $(window).width();
-      let new_height = $(window).height();
-
-      if (width !== new_width || height !== new_height) {
-        self.adjustVideoContainers();
-      }
-    });
-
-    this.$eventBus.$on("leaveMeeting", () => {
-      $.ajax({
-        url: "/associate/meeting/leave",
-        method: "POST",
-        headers: {
-          "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content")
-        },
-        error: jXhr => {
-          console.error(getSingleError(jXhr));
-        }
-      });
-      this.$store.dispatch("leaveMeeting");
-      this.disconnect();
-    });
-  }
-}
+};
 </script>
 </script>

+ 25 - 0
resources/sass/app.scss

@@ -39,6 +39,31 @@ $orange: #F08322;
 	}
 	}
 }
 }
 
 
+/* Animation */
+
+.tiles-enter-active,
+.tiles-leave-active {
+    transition: all 0.5s;
+}
+
+.tiles-enter,
+.tiles-leave-to {
+    opacity: 0;
+    transform: scale(0.3);
+}
+
+/* MC */
+
+.mc-page {
+    overflow: hidden;
+}
+
+#meetingsApp.pro {
+    overflow-y: scroll;
+    overflow-x: hidden;
+    height: 100vh;
+}
+
 /* Client App */
 /* Client App */
 
 
 #meetingsApp.client {
 #meetingsApp.client {

+ 1 - 1
resources/views/client/index.blade.php

@@ -1,6 +1,6 @@
 @extends('layouts.client')
 @extends('layouts.client')
 @section('content')
 @section('content')
 <v-app id="meetingsApp" class="client">
 <v-app id="meetingsApp" class="client">
-    <client-entrance lobby_uid="cc2fd3ed-3279-4453-959c-d9908b4946cc"></client-entrance>
+    <client-entrance :lobby-prop="{!! str_replace('"','\'',str_replace('\'','\\\'',json_encode($lobbyModel))) !!}"></client-entrance>
 </v-app>
 </v-app>
 @endsection
 @endsection

+ 2 - 0
resources/views/layouts/client.blade.php

@@ -3,6 +3,8 @@
 <head>
 <head>
     <meta charset="utf-8">
     <meta charset="utf-8">
     <meta http-equiv="X-UA-Compatible" content="IE=edge">
     <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <!-- CSRF Token -->
+    <meta name="csrf-token" content="{{ csrf_token() }}">
     <title>Stag | Pro</title>
     <title>Stag | Pro</title>
     <!-- Tell the browser to be responsive to screen width -->
     <!-- Tell the browser to be responsive to screen width -->
     <meta name="viewport" content="width=device-width, initial-scale=1">
     <meta name="viewport" content="width=device-width, initial-scale=1">

+ 4 - 2
resources/views/mc.blade.php

@@ -5,18 +5,20 @@
     <meta name="viewport"
     <meta name="viewport"
           content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
           content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
     <meta http-equiv="X-UA-Compatible" content="ie=edge">
     <meta http-equiv="X-UA-Compatible" content="ie=edge">
+    <!-- CSRF Token -->
+    <meta name="csrf-token" content="{{ csrf_token() }}">
     <script src="{{ mix('js/app.js') }}" defer></script>
     <script src="{{ mix('js/app.js') }}" defer></script>
     <link rel="stylesheet" href="/css/bootstrap.min.css">
     <link rel="stylesheet" href="/css/bootstrap.min.css">
     <link href="{{ mix('css/app.css') }}" rel="stylesheet">
     <link href="{{ mix('css/app.css') }}" rel="stylesheet">
     <link href="https://cdn.jsdelivr.net/npm/@mdi/font@4.x/css/materialdesignicons.min.css" rel="stylesheet">
     <link href="https://cdn.jsdelivr.net/npm/@mdi/font@4.x/css/materialdesignicons.min.css" rel="stylesheet">
     <title>Meeting Center</title>
     <title>Meeting Center</title>
 </head>
 </head>
-<body class="d-flex align-items-stretch h-100">
+<body class="d-flex align-items-stretch h-100 mc-page">
     <div class="row w-100">
     <div class="row w-100">
         <div class="col-9 pr-0 app-left-panel">
         <div class="col-9 pr-0 app-left-panel">
             <iframe src="{{ $page }}" frameborder="0" class="h-100 w-100"></iframe>
             <iframe src="{{ $page }}" frameborder="0" class="h-100 w-100"></iframe>
         </div>
         </div>
-        <div id="meetingsApp" class="col-3 border-left app-right-panel">
+        <div id="meetingsApp" class="pro col-3 border-left app-right-panel">
             <meetings-app-root :user-prop="{!! str_replace('"','\'',str_replace('\'','\\\'',json_encode($user))) !!}"></meetings-app-root>
             <meetings-app-root :user-prop="{!! str_replace('"','\'',str_replace('\'','\\\'',json_encode($user))) !!}"></meetings-app-root>
         </div>
         </div>
     </div>
     </div>

+ 8 - 2
routes/web.php

@@ -1,6 +1,7 @@
 <?php
 <?php
 
 
 use Illuminate\Support\Facades\Route;
 use Illuminate\Support\Facades\Route;
+use App\Models\Lobby;
 
 
 /*
 /*
 |--------------------------------------------------------------------------
 |--------------------------------------------------------------------------
@@ -56,6 +57,11 @@ Route::middleware('ensureValidSession')->group(function(){
 
 
 });
 });
 
 
-Route::get('/client', 'ClientController@entrance')->name('client-entrance');
-
 Route::post('/post-to-api', 'AppSessionController@postToAPI')->name('post-to-api');
 Route::post('/post-to-api', 'AppSessionController@postToAPI')->name('post-to-api');
+
+Route::get('/client/{url_slug}', 'ClientController@entrance')->name('client-entrance');
+
+Route::bind('url_slug', function($value, $route)
+{
+    return Lobby::where('url_slug', $value)->first();
+});