[WIP] Basic lobby and multibook support. Call is not working ATM. Need to fix signaling

This commit is contained in:
Sagi Dayan 2020-04-29 19:45:50 -04:00
parent 04d7211de1
commit 0e2fa21511
35 changed files with 634 additions and 257 deletions

View file

@ -0,0 +1,17 @@
'use strict'
const FileUtils = use('App/Utils/FileUtils');
class BookApiController {
async getPage({request, book, response}) {
const pageNumber = Number(request.params.pageNumber);
const file =
await FileUtils.getFile(`books/${book.book_folder}/${pageNumber}.jpg`);
if (file)
response.send(file);
else
return {
code: 404, message: 'no file'
}
}
}
module.exports = BookApiController

View file

@ -8,7 +8,7 @@ class CdnController {
response.send(file); response.send(file);
else else
return { return {
ERROR: 'no file' code: 404, message: 'no file'
} }
} }
} }

View file

@ -1,6 +1,7 @@
'use strict' 'use strict'
const User = use('App/Models/User'); const User = use('App/Models/User');
const UserChildUtils = use('App/Utils/UserChildUtils'); const UserChildUtils = use('App/Utils/UserChildUtils');
const CallUtils = use('App/Utils/CallUtils');
const Child = use('App/Models/Child'); const Child = use('App/Models/Child');
const IceServer = use('App/Models/IceServer'); const IceServer = use('App/Models/IceServer');
const UserChannel = use('App/Controllers/Ws/UserChannelController') const UserChannel = use('App/Controllers/Ws/UserChannelController')
@ -47,6 +48,7 @@ class CallSession {
this.onCallEndedCallback = onCallEndedCallback; this.onCallEndedCallback = onCallEndedCallback;
this.callId = callModel.id; this.callId = callModel.id;
this.callModel = callModel; this.callModel = callModel;
this.callBooks = null;
this.hostId = 2; this.hostId = 2;
this.state = callModel.state; this.state = callModel.state;
this.sessionState = {page: 'lobby', activity: {type: null, model: null}}; this.sessionState = {page: 'lobby', activity: {type: null, model: null}};
@ -90,6 +92,9 @@ class CallSession {
} }
async registerUser(user, socket) { async registerUser(user, socket) {
if (!this.child) this.child = await this.callModel.child().fetch(); if (!this.child) this.child = await this.callModel.child().fetch();
if (!this.callBooks)
this.callBooks = await CallUtils.getBooks(
this.callModel.parent_id, this.callModel.guest_id);
let isParent = this.parent.id === user.id; let isParent = this.parent.id === user.id;
let peerId = isParent ? this.guest.id : this.parent.id; let peerId = isParent ? this.guest.id : this.parent.id;
if (isParent) { if (isParent) {
@ -146,6 +151,7 @@ class CallSession {
socket.emit('call:standby', { socket.emit('call:standby', {
iceServers, iceServers,
peerId, peerId,
books: this.callBooks,
child: this.child.toJSON(), child: this.child.toJSON(),
users: await Promise.all( users: await Promise.all(
[this.callModel.parent().fetch(), this.callModel.guest().fetch()]), [this.callModel.parent().fetch(), this.callModel.guest().fetch()]),
@ -158,6 +164,7 @@ class CallSession {
socket.emit('call:start', { socket.emit('call:start', {
iceServers, iceServers,
peerId, peerId,
books: this.callBooks,
child: this.child.toJSON(), child: this.child.toJSON(),
users: await Promise.all( users: await Promise.all(
[this.callModel.parent().fetch(), this.callModel.guest().fetch()]), [this.callModel.parent().fetch(), this.callModel.guest().fetch()]),
@ -215,11 +222,16 @@ class CallSession {
return true; return true;
} }
async onHostChanged(payload) { async onHostChanged(payload) {
const {peerId, hostId} = payload; const {peerId, userId} = payload;
this.hostId = hostId; this.hostId = this.hostId === userId ? peerId : userId;
this.userMap.get(peerId).socket.emit('call:host:changed', {hostId}); console.log('Host: ', this.hostId);
console.log( this.userMap.get(userId).socket.emit(
`[Signal] [host] [changed] [hostId=${hostId}] ${from} -> ${to}`); 'call:host:changed', {hostId: this.hostId});
try {
this.userMap.get(peerId).socket.emit(
'call:host:changed', {hostId: this.hostId});
} catch (e) {
}
return true; return true;
} }
} }

View file

@ -0,0 +1,40 @@
'use strict'
/** @typedef {import('@adonisjs/framework/src/Request')} Request */
/** @typedef {import('@adonisjs/framework/src/Response')} Response */
/** @typedef {import('@adonisjs/framework/src/View')} View */
const Book = use('App/Models/Book');
const UserChildUtils = use('App/Utils/UserChildUtils');
class BookPageAuth {
/**
* @param {object} ctx
* @param {Request} ctx.request
* @param {Function} next
*/
async handle(ctx, next) {
const {request, auth, response} = ctx;
// call next to advance the request
const user = auth.user;
const bookId = request.params.bookId;
const book = await Book.find(bookId);
if (!book) {
response.status(404);
response.send({code: 404, message: 'Book not found'});
return;
}
ctx.book = book;
if (book.user_id) {
// Belongs to a user. Check if the book user has a connection with this
// user
if (book.user_id === user.id)
await next();
else {
const ownerConnections =
await UserChildUtils.getUserConnections(book.user_id);
console.log(ownerConnections);
}
}
await next();
}
}
module.exports = BookPageAuth

View file

@ -4,6 +4,9 @@
const Model = use('Model') const Model = use('Model')
class Book extends Model { class Book extends Model {
static get hidden() {
return ['user_id'];
}
user() { user() {
if (!this.user_id) return null; if (!this.user_id) return null;
return this.belongsTo('App/Models/User'); return this.belongsTo('App/Models/User');

View file

@ -3,6 +3,7 @@
/** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */ /** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */
const Model = use('Model') const Model = use('Model')
const User = use('App/Models/User'); const User = use('App/Models/User');
const Book = use('App/Models/Book');
class Call extends Model { class Call extends Model {
parent() { parent() {

18
app/Utils/CallUtils.js Normal file
View file

@ -0,0 +1,18 @@
const Book = use('App/Models/Book');
class CallUtils {
static async getBooks(parent_id, guest_id) {
const result = await Book.query()
.where({user_id: parent_id})
.orWhere({user_id: guest_id})
.orWhere({user_id: null})
.fetch();
if (!result.rows.length)
return [];
else
return result.rows;
}
}
module.exports = CallUtils;

View file

@ -15,6 +15,7 @@ class FileUtils {
try { try {
return await Drive.get(filename) return await Drive.get(filename)
} catch (e) { } catch (e) {
console.error(e);
return null; return null;
} }
} }

View file

@ -8,6 +8,7 @@ class BookSchema extends Schema {
this.create('books', (table) => { this.create('books', (table) => {
table.increments() table.increments()
table.bigInteger('user_id'); table.bigInteger('user_id');
table.string('title', 80).notNullable();
table.integer('pages').notNullable(); table.integer('pages').notNullable();
table.string('book_folder').notNullable(); table.string('book_folder').notNullable();
table.boolean('ltr').default(true); table.boolean('ltr').default(true);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -6541,10 +6541,24 @@ label.panel-block {
background-color: #fafafa; background-color: #fafafa;
padding: 3rem 1.5rem 6rem; } padding: 3rem 1.5rem 6rem; }
.navbar {
background: #fafafa; }
.hero-bg { .hero-bg {
background: repeating-linear-gradient(-45deg, transparent, transparent 1em, rgba(127, 215, 245, 0.4) 0, rgba(127, 215, 245, 0.1) 2em, transparent 0, transparent 1em, rgba(127, 215, 245, 0.3) 0, rgba(127, 215, 245, 0.2) 4em, transparent 0, transparent 1em, rgba(192, 235, 250, 0.6) 0, rgba(192, 235, 250, 0.2) 2em), repeating-linear-gradient(45deg, transparent, transparent 1em, rgba(127, 215, 245, 0.4) 0, rgba(127, 215, 245, 0.1) 2em, transparent 0, transparent 1em, rgba(127, 215, 245, 0.3) 0, rgba(127, 215, 245, 0.2) 4em, transparent 0, transparent 1em, rgba(192, 235, 250, 0.4) 0, rgba(192, 235, 250, 0.1) 2em), #FFF; background: repeating-linear-gradient(-45deg, transparent, transparent 1em, rgba(127, 215, 245, 0.4) 0, rgba(127, 215, 245, 0.1) 2em, transparent 0, transparent 1em, rgba(127, 215, 245, 0.3) 0, rgba(127, 215, 245, 0.2) 4em, transparent 0, transparent 1em, rgba(192, 235, 250, 0.6) 0, rgba(192, 235, 250, 0.2) 2em), repeating-linear-gradient(45deg, transparent, transparent 1em, rgba(127, 215, 245, 0.4) 0, rgba(127, 215, 245, 0.1) 2em, transparent 0, transparent 1em, rgba(127, 215, 245, 0.3) 0, rgba(127, 215, 245, 0.2) 4em, transparent 0, transparent 1em, rgba(192, 235, 250, 0.4) 0, rgba(192, 235, 250, 0.1) 2em), #FFF;
background-blend-mode: multiply; } background-blend-mode: multiply; }
.navbar-menu.is-active {
animation: expand-animation .1s ease-in; }
@keyframes expand-animation {
from {
height: 0;
transform: scaleY(0); }
to {
transform: scaleY(1);
height: auto; } }
.hero .navbar.darken { .hero .navbar.darken {
background: rgba(0, 0, 0, 0.7); background: rgba(0, 0, 0, 0.7);
/* Old browsers */ /* Old browsers */
@ -6846,13 +6860,19 @@ label.panel-block {
.is-fullheight { .is-fullheight {
min-height: calc(100vh - ( 3.25rem )); min-height: calc(100vh - ( 3.25rem ));
max-height: calc(100vh - ( 3.25rem )); max-height: calc(100vh - ( 3.25rem ));
height: calc(100vh - ( 3.25rem )); height: calc(100vh - ( 3.25rem )); }
display: flex;
flex-direction: row;
justify-content: stretch; }
.is-fullheight .column { .is-fullheight .column {
overflow-y: auto; } overflow-y: auto; }
.is-fullwidth {
width: 100vw;
overflow: hidden; }
.app-content {
display: flex;
flex-direction: row;
justify-content: center; }
.avatar-badge-image { .avatar-badge-image {
display: table; display: table;
margin: auto; } margin: auto; }
@ -6917,16 +6937,37 @@ label.panel-block {
.flipbook { .flipbook {
width: 100%; width: 100%;
height: 60vh; } height: 75vh;
overflow-y: visible; }
.book-flip-buttons {
height: 100%; }
.video-strip { .video-strip {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-content: center; } align-content: center;
max-height: 15vh; }
video { video {
margin-left: 5px; margin-left: 5px;
margin-right: 5px; margin-right: 5px;
background-color: rgba(56, 181, 187, 0.19); background-color: rgba(56, 181, 187, 0.19);
border-radius: 15px; border-radius: 15px;
border: solid 1px rgba(56, 181, 187, 0.3); } border: solid 1px rgba(56, 181, 187, 0.3);
flex-basis: 100%;
max-width: 15vh; }
.book-thumb {
cursor: pointer;
transition: all .2s;
z-index: inherit;
flex-basis: 12%;
text-align: center; }
.book-thumb:hover {
transform: scale(1.1);
z-index: 10; }
.book-thumb:disabled:hover {
cursor: not-allowed;
transform: scale(1); }

View file

@ -5,25 +5,45 @@
@import './mixins.scss'; @import './mixins.scss';
@import "../../node_modules/bulma/bulma.sass"; @import "../../node_modules/bulma/bulma.sass";
// @import '../../node_modules/animate.css/source/_base.css'; // @import '../../node_modules/animate.css/source/_base.css';
.navbar {
background:#fafafa;
}
.hero-bg{ .hero-bg{
@include pattern(#c0ebfa, #7FD7F5); @include pattern(#c0ebfa, #7FD7F5);
} }
.navbar-menu{
&.is-active{
// transition: all;
// position: relative;
// top:0;
animation: expand-animation .1s ease-in;
}
}
@keyframes expand-animation{
from{
height: 0;
transform: scaleY(0);
}
to{
transform: scaleY(1);
height: auto;
}
}
.hero { .hero {
.navbar.darken { .navbar.darken {
@include linearGradient(rgba(0, 0, 0, .7), rgba(0, 0, 0, 0)); @include linearGradient(rgba(0, 0, 0, .7), rgba(0, 0, 0, 0));
} }
// .navbar-item{ // .navbar-item{
// color: $primary; // color: $primary;
// } // }
} }
.has-inner-shadow-bottom{ .has-inner-shadow-bottom{
box-shadow: inset 0px -10px 15px -11px rgba(0,0,0,0.75); box-shadow: inset 0px -10px 15px -11px rgba(0,0,0,0.75);
} }
.has-inner-shadow-top{ .has-inner-shadow-top{
box-shadow: inset 0px 10px 15px -11px rgba(0,0,0,0.75); box-shadow: inset 0px 10px 15px -11px rgba(0,0,0,0.75);
} }
.has-shadow-top{ .has-shadow-top{
box-shadow: 0px 10px 15px -11px rgba(0,0,0,0.75); box-shadow: 0px 10px 15px -11px rgba(0,0,0,0.75);
} }
@ -32,49 +52,49 @@ $marginKey: 'm';
$paddingKey: 'p'; $paddingKey: 'p';
$separator: '-'; $separator: '-';
$sizes: ( $sizes: (
('none', 0), ('none', 0),
('xxs', 0.75), ('xxs', 0.75),
('xs', 0.25), ('xs', 0.25),
('sm', 0.5), ('sm', 0.5),
('md', 1), ('md', 1),
('lg', 2), ('lg', 2),
('xl', 4), ('xl', 4),
('xxl', 8), ('xxl', 8),
); );
$positions: ( $positions: (
('t', 'top'), ('t', 'top'),
('r', 'right'), ('r', 'right'),
('b', 'bottom'), ('b', 'bottom'),
('l', 'left') ('l', 'left')
); );
@function sizeValue($key, $value) { @function sizeValue($key, $value) {
@return if($key == 'none', 0, $value + $sizeUnit); @return if($key == 'none', 0, $value + $sizeUnit);
} }
@each $size in $sizes { @each $size in $sizes {
$sizeKey: nth($size, 1); $sizeKey: nth($size, 1);
$sizeValue: nth($size, 2); $sizeValue: nth($size, 2);
.#{$marginKey}#{$separator}#{$sizeKey} { .#{$marginKey}#{$separator}#{$sizeKey} {
margin: sizeValue($sizeKey, $sizeValue); margin: sizeValue($sizeKey, $sizeValue);
} }
.#{$paddingKey}#{$separator}#{$sizeKey} { .#{$paddingKey}#{$separator}#{$sizeKey} {
padding: sizeValue($sizeKey, $sizeValue); padding: sizeValue($sizeKey, $sizeValue);
} }
@each $position in $positions { @each $position in $positions {
$posKey: nth($position, 1); $posKey: nth($position, 1);
$posValue: nth($position, 2); $posValue: nth($position, 2);
.#{$marginKey}#{$separator}#{$posKey}#{$separator}#{$sizeKey} { .#{$marginKey}#{$separator}#{$posKey}#{$separator}#{$sizeKey} {
margin-#{$posValue}: sizeValue($sizeKey, $sizeValue); margin-#{$posValue}: sizeValue($sizeKey, $sizeValue);
} }
.#{$paddingKey}#{$separator}#{$posKey}#{$separator}#{$sizeKey} { .#{$paddingKey}#{$separator}#{$posKey}#{$separator}#{$sizeKey} {
padding-#{$posValue}: sizeValue($sizeKey, $sizeValue); padding-#{$posValue}: sizeValue($sizeKey, $sizeValue);
} }
}
}
.m-t-a{
margin-top: auto;
} }
}
.m-t-a{
margin-top: auto;
}
.m-b-a{ .m-b-a{
margin-bottom: auto; margin-bottom: auto;
} }
@ -84,48 +104,24 @@ $positions: (
.m-r-a{ .m-r-a{
margin-right: auto; margin-right: auto;
} }
// .app {
// .columns{
// margin-bottom: 0px !important;
// max-height: calc(100vh - 12.25rem);
// height: calc(100vh - 12.25rem);
// }
// }
.app-content{
// height: 100vh;
}
.sidebar{ .sidebar{
// margin: auto;
flex-direction: column; flex-direction: column;
max-width: 5em; max-width: 5em;
width: 5em; width: 5em;
height: 100%; height: 100%;
// background-color:rgba(127, 88, 145, 0.7);
// color: $beige-lighter;
border-right: thin #ccc solid; border-right: thin #ccc solid;
} }
.menu-titles{ .menu-titles{
margin-top: -2em; margin-top: -2em;
} }
.is-sidebar {
// transform: translate(15%, 0);
// background-color: rgba(0, 0, 0, .3);
}
.sidebar-menu{ .sidebar-menu{
position: absolute; position: absolute;
bottom: 25px; bottom: 25px;
left: 1em; left: 1em;
} }
.sideway-text{
// transform: translate(0, -50%);
// display: inline;
// margin: 0 0;
}
.sideway-letter{ .sideway-letter{
// width: 50%;
// height: 2em;
transform: rotate(-90deg); transform: rotate(-90deg);
margin: -.8em auto; margin: -.8em auto;
} }
@ -134,14 +130,20 @@ $positions: (
min-height: calc(100vh - ( #{$navbar-height} ) ); min-height: calc(100vh - ( #{$navbar-height} ) );
max-height: calc(100vh - ( #{$navbar-height} ) ); max-height: calc(100vh - ( #{$navbar-height} ) );
height: calc(100vh - ( #{$navbar-height} ) ); height: calc(100vh - ( #{$navbar-height} ) );
display: flex;
flex-direction: row;
justify-content: stretch;
.column{ .column{
overflow-y: auto; overflow-y: auto;
} }
} }
.is-fullwidth{
width: 100vw;
overflow: hidden;
}
.app-content{
display: flex;
flex-direction: row;
justify-content: center;
}
.avatar-badge-image{ .avatar-badge-image{
display: table; display: table;
margin: auto; margin: auto;
@ -223,18 +225,42 @@ $positions: (
// TODO: Remove this - make generic // TODO: Remove this - make generic
.flipbook { .flipbook {
width: 100%; width: 100%;
height: 60vh; height: 75vh;
overflow-y: visible;
}
.book-flip-buttons{
height: 100%;
} }
.video-strip{ .video-strip{
display: flex; display: flex;
justify-content: center; justify-content: center;
align-content:center; align-content:center;
max-height: 15vh;
} }
video{ video{
margin-left: 5px; margin-left: 5px;
margin-right: 5px; margin-right: 5px;
background-color:rgba(56, 181, 187, 0.19); background-color:rgba(56, 181, 187, 0.19);
border-radius: 15px; border-radius: 15px;
border: solid 1px rgba(56, 181, 187, 0.30); border: solid 1px rgba(56, 181, 187, 0.30);
flex-basis: 100%;
max-width: 15vh;
} }
.book-thumb{
cursor: pointer;
transition: all .2s;
z-index: inherit;
flex-basis: 12%;
text-align: center;
&:hover{
transform: scale(1.1);
z-index: 10;
// box-shadow: 10px 0px 15px -13px rgba(0,0,0,0.35);
}
}
.book-thumb:disabled:hover{
cursor: not-allowed;
transform: scale(1);
}

View file

@ -1,6 +1,7 @@
<template> <template>
<div class="app"> <div class="app">
<!-- <Header :appName="appName" /> --> <!-- <Header :appName="appName" /> -->
<TopNavbar />
<div class="loading" v-if="loading"> <div class="loading" v-if="loading">
<Loading /> <Loading />
</div> </div>
@ -13,15 +14,10 @@
@onClose="onNotificationClose(notification)" @onClose="onNotificationClose(notification)"
/> />
</div> </div>
<div class="columns m-t-xs is-fullheight"> <div class="app-content m-t-xs is-fullheight">
<div class="column sidebar"> <div class="application">
<SideBar :title="appName" :menu="menu" :appName="appName" /> <router-view></router-view>
</div> </div>
<section class="section column app-content">
<div class="container">
<router-view></router-view>
</div>
</section>
</div> </div>
</div> </div>
</div> </div>
@ -33,42 +29,20 @@ import Services from "../services/index";
import Notification from "../shared/components/Notification.vue"; import Notification from "../shared/components/Notification.vue";
import { mapActions, mapGetters } from "vuex"; import { mapActions, mapGetters } from "vuex";
import Loading from "../shared/components/Loading/Loading.vue"; import Loading from "../shared/components/Loading/Loading.vue";
import TopNavbar from "../shared/components/TopNavbar.vue";
import WebsocketService from "./scripts/websocket.service"; import WebsocketService from "./scripts/websocket.service";
// import AppRouter from "./router/router.vue"; // import AppRouter from "./router/router.vue";
import {
default as SideBar,
IMenuItem
} from "../shared/components/SideBar/SideBar.vue";
const menu: IMenuItem[] = [
{
href: "/",
text: "Home",
isRouterLink: true,
icon: "fa fa-home"
},
{
href: "/settings",
isRouterLink: true,
text: "Settings",
icon: "fa fa-gears"
},
{
isRouterLink: false,
href: "/logout",
text: "Logout",
icon: "fa fa-sign-out"
}
];
// Services.ApiService.getConnections(); // Services.ApiService.getConnections();
export default { export default {
name: "App", name: "App",
// router: AppRouter, // router: AppRouter,
components: { components: {
SideBar,
Header, Header,
Notification, Notification,
Loading Loading,
TopNavbar
}, },
async created() { async created() {
await this.getUser(); await this.getUser();
@ -90,7 +64,6 @@ export default {
data() { data() {
return { return {
appName: "Seepur", appName: "Seepur",
menu,
loading: true, loading: true,
ws: null ws: null
}; };
@ -104,7 +77,7 @@ export default {
message: `New call from ${payload.child.name}`, message: `New call from ${payload.child.name}`,
level: "success" level: "success"
}); });
this.$router.push({ path: `/call/${payload.callId}` }); this.$router.replace({ path: `/call/${payload.callId}` });
}, },
onNotificationClose(notification) { onNotificationClose(notification) {
this.dismissNotification(notification.id); this.dismissNotification(notification.id);

View file

@ -6,12 +6,13 @@ export default class CallManager {
private localStream: MediaStream; private localStream: MediaStream;
private remoteStream: MediaStream; private remoteStream: MediaStream;
private signalingChannel; private signalingChannel;
private needToAddStream: boolean = true;
private emitter = new EventEmitter(); private emitter = new EventEmitter();
private pc: RTCPeerConnection; private pc: RTCPeerConnection;
public child; public child;
public peer = { avatar: '' }; public peer = { avatar: '' };
public isHost = false; public isHost = false;
public books = [];
public currentActivity = null;
constructor(private ws: WebSocketService, private callId: number, private userId: number) { constructor(private ws: WebSocketService, private callId: number, private userId: number) {
this.inCall = false; this.inCall = false;
this.peerId = null; this.peerId = null;
@ -60,10 +61,11 @@ export default class CallManager {
...payload ...payload
}) })
} }
async onCallStart(payload: { iceServers: RTCIceServer[], peerId: number, users: any[], child: any, hostId: number }) { async onCallStart(payload: { iceServers: RTCIceServer[], peerId: number, users: any[], child: any, hostId: number, books: any[] }) {
console.log('onCallStart'); console.log('onCallStart');
console.log(payload); console.log(payload);
this.peerId = payload.peerId; this.peerId = payload.peerId;
this.books = payload.books;
this.isHost = this.peerId === payload.hostId; this.isHost = this.peerId === payload.hostId;
this.pc = new RTCPeerConnection({ iceServers: payload.iceServers }); this.pc = new RTCPeerConnection({ iceServers: payload.iceServers });
this.child = payload.child; this.child = payload.child;
@ -84,10 +86,11 @@ export default class CallManager {
return true; return true;
} }
async onCallStandby(payload: { iceServers: RTCIceServer[], peerId: number, users: any[], child: any, hostId: number }) { async onCallStandby(payload: { iceServers: RTCIceServer[], peerId: number, users: any[], child: any, hostId: number, books: any[] }) {
console.log('onCallStandby'); console.log('onCallStandby');
console.log(payload); console.log(payload);
this.peerId = payload.peerId; this.peerId = payload.peerId;
this.books = payload.books;
this.isHost = this.peerId === payload.hostId; this.isHost = this.peerId === payload.hostId;
this.pc = new RTCPeerConnection({ iceServers: payload.iceServers }); this.pc = new RTCPeerConnection({ iceServers: payload.iceServers });
this.child = payload.child; this.child = payload.child;
@ -102,6 +105,10 @@ export default class CallManager {
return true; return true;
} }
public selectBook(index: number) {
this.currentActivity = this.books[index];
}
setupPeerConnectionListeners() { setupPeerConnectionListeners() {
this.pc.addEventListener("icecandidate", this.onLocalIce.bind(this)); this.pc.addEventListener("icecandidate", this.onLocalIce.bind(this));
this.pc.addEventListener('connectionstatechange', event => { this.pc.addEventListener('connectionstatechange', event => {
@ -174,10 +181,7 @@ export default class CallManager {
}; };
changeHost() { changeHost() {
this.isHost = !this.isHost; this.send('call:host:changed', {});
const hostId = this.isHost ? this.userId : this.peerId;
this.emit(ECallEvents.CALL_HOST_CHANGED, { hostId });
this.send('call:host:changed', { hostId });
} }
onRemoteHostChanged(payload) { onRemoteHostChanged(payload) {

View file

@ -1207,7 +1207,7 @@ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=
}),_vm._v(" "),_c('div',{ref:"viewport",staticClass:"viewport",class:{ }),_vm._v(" "),_c('div',{ref:"viewport",staticClass:"viewport",class:{
zoom: _vm.zooming || _vm.zoom > 1, zoom: _vm.zooming || _vm.zoom > 1,
'drag-to-scroll': _vm.dragToScroll 'drag-to-scroll': _vm.dragToScroll
},style:({ cursor: _vm.cursor == 'grabbing' ? 'grabbing' : 'auto' }),on:{"touchmove":_vm.onTouchMove,"pointermove":_vm.onPointerMove,"mousemove":_vm.onMouseMove,"touchend":_vm.onTouchEnd,"touchcancel":_vm.onTouchEnd,"pointerup":_vm.onPointerUp,"pointercancel":_vm.onPointerUp,"mouseup":_vm.onMouseUp,"wheel":_vm.onWheel}},[_c('div',{staticClass:"container",style:({ transform: ("scale(" + _vm.zoom + ")"), })},[_c('div',{style:({ transform: ("translateX(" + _vm.centerOffsetSmoothed + "px)") })},[(_vm.showLeftPage)?_c('img',{staticClass:"page fixed",style:({ },style:({ cursor: _vm.cursor == 'grabbing' ? 'grabbing' : 'auto' }),on:{"touchmove":_vm.onTouchMove,"pointermove":_vm.onPointerMove,"mousemove":_vm.onMouseMove,"touchend":_vm.onTouchEnd,"touchcancel":_vm.onTouchEnd,"pointerup":_vm.onPointerUp,"pointercancel":_vm.onPointerUp,"mouseup":_vm.onMouseUp,"wheel":_vm.onWheel}},[_c('div',{staticClass:"book-container",style:({ transform: ("scale(" + _vm.zoom + ")"), })},[_c('div',{style:({ transform: ("translateX(" + _vm.centerOffsetSmoothed + "px)") })},[(_vm.showLeftPage)?_c('img',{staticClass:"page fixed",style:({
width: _vm.pageWidth + 'px', width: _vm.pageWidth + 'px',
height: _vm.pageHeight + 'px', height: _vm.pageHeight + 'px',
left: _vm.xMargin + 'px', left: _vm.xMargin + 'px',
@ -1244,11 +1244,11 @@ var __vue_staticRenderFns__ = [];
/* style */ /* style */
var __vue_inject_styles__ = function (inject) { var __vue_inject_styles__ = function (inject) {
if (!inject) { return } if (!inject) { return }
inject("data-v-0519f6d1_0", { source: ".viewport[data-v-0519f6d1]{-webkit-overflow-scrolling:touch;width:100%;height:100%}.viewport.zoom[data-v-0519f6d1]{overflow:scroll}.viewport.zoom.drag-to-scroll[data-v-0519f6d1]{overflow:hidden}.container[data-v-0519f6d1]{position:relative;width:100%;height:100%;transform-origin:top left;-webkit-user-select:none;-ms-user-select:none;user-select:none}.click-to-flip[data-v-0519f6d1]{position:absolute;width:50%;height:100%;top:0;-webkit-user-select:none;-ms-user-select:none;user-select:none}.click-to-flip.left[data-v-0519f6d1]{left:0}.click-to-flip.right[data-v-0519f6d1]{right:0}.bounding-box[data-v-0519f6d1]{position:absolute;-webkit-user-select:none;-ms-user-select:none;user-select:none}.page[data-v-0519f6d1]{position:absolute;-webkit-backface-visibility:hidden;backface-visibility:hidden}.polygon[data-v-0519f6d1]{position:absolute;top:0;left:0;background-repeat:no-repeat;-webkit-backface-visibility:hidden;backface-visibility:hidden;transform-origin:center left}.polygon.blank[data-v-0519f6d1]{background-color:#ddd}.polygon .lighting[data-v-0519f6d1]{width:100%;height:100%}", map: undefined, media: undefined }); inject("data-v-16acf8ed_0", { source: ".viewport[data-v-16acf8ed]{-webkit-overflow-scrolling:touch;width:100%;height:95%}.viewport.zoom[data-v-16acf8ed]{overflow:scroll}.viewport.zoom.drag-to-scroll[data-v-16acf8ed]{overflow:hidden}.book-container[data-v-16acf8ed]{position:relative;width:100%;height:95%;transform-origin:top left;-webkit-user-select:none;-ms-user-select:none;user-select:none}.click-to-flip[data-v-16acf8ed]{position:absolute;width:50%;height:100%;top:0;-webkit-user-select:none;-ms-user-select:none;user-select:none}.click-to-flip.left[data-v-16acf8ed]{left:0}.click-to-flip.right[data-v-16acf8ed]{right:0}.bounding-box[data-v-16acf8ed]{position:absolute;-webkit-user-select:none;-ms-user-select:none;user-select:none}.page[data-v-16acf8ed]{position:absolute;-webkit-backface-visibility:hidden;backface-visibility:hidden}.polygon[data-v-16acf8ed]{position:absolute;top:0;left:0;background-repeat:no-repeat;-webkit-backface-visibility:hidden;backface-visibility:hidden;transform-origin:center left}.polygon.blank[data-v-16acf8ed]{background-color:#ddd}.polygon .lighting[data-v-16acf8ed]{width:100%;height:100%}", map: undefined, media: undefined });
}; };
/* scoped */ /* scoped */
var __vue_scope_id__ = "data-v-0519f6d1"; var __vue_scope_id__ = "data-v-16acf8ed";
/* module identifier */ /* module identifier */
var __vue_module_identifier__ = undefined; var __vue_module_identifier__ = undefined;
/* functional template */ /* functional template */

View file

@ -1205,7 +1205,7 @@ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=
}),_vm._v(" "),_c('div',{ref:"viewport",staticClass:"viewport",class:{ }),_vm._v(" "),_c('div',{ref:"viewport",staticClass:"viewport",class:{
zoom: _vm.zooming || _vm.zoom > 1, zoom: _vm.zooming || _vm.zoom > 1,
'drag-to-scroll': _vm.dragToScroll 'drag-to-scroll': _vm.dragToScroll
},style:({ cursor: _vm.cursor == 'grabbing' ? 'grabbing' : 'auto' }),on:{"touchmove":_vm.onTouchMove,"pointermove":_vm.onPointerMove,"mousemove":_vm.onMouseMove,"touchend":_vm.onTouchEnd,"touchcancel":_vm.onTouchEnd,"pointerup":_vm.onPointerUp,"pointercancel":_vm.onPointerUp,"mouseup":_vm.onMouseUp,"wheel":_vm.onWheel}},[_c('div',{staticClass:"container",style:({ transform: ("scale(" + _vm.zoom + ")"), })},[_c('div',{style:({ transform: ("translateX(" + _vm.centerOffsetSmoothed + "px)") })},[(_vm.showLeftPage)?_c('img',{staticClass:"page fixed",style:({ },style:({ cursor: _vm.cursor == 'grabbing' ? 'grabbing' : 'auto' }),on:{"touchmove":_vm.onTouchMove,"pointermove":_vm.onPointerMove,"mousemove":_vm.onMouseMove,"touchend":_vm.onTouchEnd,"touchcancel":_vm.onTouchEnd,"pointerup":_vm.onPointerUp,"pointercancel":_vm.onPointerUp,"mouseup":_vm.onMouseUp,"wheel":_vm.onWheel}},[_c('div',{staticClass:"book-container",style:({ transform: ("scale(" + _vm.zoom + ")"), })},[_c('div',{style:({ transform: ("translateX(" + _vm.centerOffsetSmoothed + "px)") })},[(_vm.showLeftPage)?_c('img',{staticClass:"page fixed",style:({
width: _vm.pageWidth + 'px', width: _vm.pageWidth + 'px',
height: _vm.pageHeight + 'px', height: _vm.pageHeight + 'px',
left: _vm.xMargin + 'px', left: _vm.xMargin + 'px',
@ -1242,11 +1242,11 @@ var __vue_staticRenderFns__ = [];
/* style */ /* style */
var __vue_inject_styles__ = function (inject) { var __vue_inject_styles__ = function (inject) {
if (!inject) { return } if (!inject) { return }
inject("data-v-0519f6d1_0", { source: ".viewport[data-v-0519f6d1]{-webkit-overflow-scrolling:touch;width:100%;height:100%}.viewport.zoom[data-v-0519f6d1]{overflow:scroll}.viewport.zoom.drag-to-scroll[data-v-0519f6d1]{overflow:hidden}.container[data-v-0519f6d1]{position:relative;width:100%;height:100%;transform-origin:top left;-webkit-user-select:none;-ms-user-select:none;user-select:none}.click-to-flip[data-v-0519f6d1]{position:absolute;width:50%;height:100%;top:0;-webkit-user-select:none;-ms-user-select:none;user-select:none}.click-to-flip.left[data-v-0519f6d1]{left:0}.click-to-flip.right[data-v-0519f6d1]{right:0}.bounding-box[data-v-0519f6d1]{position:absolute;-webkit-user-select:none;-ms-user-select:none;user-select:none}.page[data-v-0519f6d1]{position:absolute;-webkit-backface-visibility:hidden;backface-visibility:hidden}.polygon[data-v-0519f6d1]{position:absolute;top:0;left:0;background-repeat:no-repeat;-webkit-backface-visibility:hidden;backface-visibility:hidden;transform-origin:center left}.polygon.blank[data-v-0519f6d1]{background-color:#ddd}.polygon .lighting[data-v-0519f6d1]{width:100%;height:100%}", map: undefined, media: undefined }); inject("data-v-16acf8ed_0", { source: ".viewport[data-v-16acf8ed]{-webkit-overflow-scrolling:touch;width:100%;height:95%}.viewport.zoom[data-v-16acf8ed]{overflow:scroll}.viewport.zoom.drag-to-scroll[data-v-16acf8ed]{overflow:hidden}.book-container[data-v-16acf8ed]{position:relative;width:100%;height:95%;transform-origin:top left;-webkit-user-select:none;-ms-user-select:none;user-select:none}.click-to-flip[data-v-16acf8ed]{position:absolute;width:50%;height:100%;top:0;-webkit-user-select:none;-ms-user-select:none;user-select:none}.click-to-flip.left[data-v-16acf8ed]{left:0}.click-to-flip.right[data-v-16acf8ed]{right:0}.bounding-box[data-v-16acf8ed]{position:absolute;-webkit-user-select:none;-ms-user-select:none;user-select:none}.page[data-v-16acf8ed]{position:absolute;-webkit-backface-visibility:hidden;backface-visibility:hidden}.polygon[data-v-16acf8ed]{position:absolute;top:0;left:0;background-repeat:no-repeat;-webkit-backface-visibility:hidden;backface-visibility:hidden;transform-origin:center left}.polygon.blank[data-v-16acf8ed]{background-color:#ddd}.polygon .lighting[data-v-16acf8ed]{width:100%;height:100%}", map: undefined, media: undefined });
}; };
/* scoped */ /* scoped */
var __vue_scope_id__ = "data-v-0519f6d1"; var __vue_scope_id__ = "data-v-16acf8ed";
/* module identifier */ /* module identifier */
var __vue_module_identifier__ = undefined; var __vue_module_identifier__ = undefined;
/* functional template */ /* functional template */

View file

@ -1388,7 +1388,7 @@
}),_vm._v(" "),_c('div',{ref:"viewport",staticClass:"viewport",class:{ }),_vm._v(" "),_c('div',{ref:"viewport",staticClass:"viewport",class:{
zoom: _vm.zooming || _vm.zoom > 1, zoom: _vm.zooming || _vm.zoom > 1,
'drag-to-scroll': _vm.dragToScroll 'drag-to-scroll': _vm.dragToScroll
},style:({ cursor: _vm.cursor == 'grabbing' ? 'grabbing' : 'auto' }),on:{"touchmove":_vm.onTouchMove,"pointermove":_vm.onPointerMove,"mousemove":_vm.onMouseMove,"touchend":_vm.onTouchEnd,"touchcancel":_vm.onTouchEnd,"pointerup":_vm.onPointerUp,"pointercancel":_vm.onPointerUp,"mouseup":_vm.onMouseUp,"wheel":_vm.onWheel}},[_c('div',{staticClass:"container",style:({ transform: ("scale(" + _vm.zoom + ")"), })},[_c('div',{style:({ transform: ("translateX(" + _vm.centerOffsetSmoothed + "px)") })},[(_vm.showLeftPage)?_c('img',{staticClass:"page fixed",style:({ },style:({ cursor: _vm.cursor == 'grabbing' ? 'grabbing' : 'auto' }),on:{"touchmove":_vm.onTouchMove,"pointermove":_vm.onPointerMove,"mousemove":_vm.onMouseMove,"touchend":_vm.onTouchEnd,"touchcancel":_vm.onTouchEnd,"pointerup":_vm.onPointerUp,"pointercancel":_vm.onPointerUp,"mouseup":_vm.onMouseUp,"wheel":_vm.onWheel}},[_c('div',{staticClass:"book-container",style:({ transform: ("scale(" + _vm.zoom + ")"), })},[_c('div',{style:({ transform: ("translateX(" + _vm.centerOffsetSmoothed + "px)") })},[(_vm.showLeftPage)?_c('img',{staticClass:"page fixed",style:({
width: _vm.pageWidth + 'px', width: _vm.pageWidth + 'px',
height: _vm.pageHeight + 'px', height: _vm.pageHeight + 'px',
left: _vm.xMargin + 'px', left: _vm.xMargin + 'px',
@ -1425,11 +1425,11 @@
/* style */ /* style */
var __vue_inject_styles__ = function (inject) { var __vue_inject_styles__ = function (inject) {
if (!inject) { return } if (!inject) { return }
inject("data-v-0519f6d1_0", { source: ".viewport[data-v-0519f6d1]{-webkit-overflow-scrolling:touch;width:100%;height:100%}.viewport.zoom[data-v-0519f6d1]{overflow:scroll}.viewport.zoom.drag-to-scroll[data-v-0519f6d1]{overflow:hidden}.container[data-v-0519f6d1]{position:relative;width:100%;height:100%;transform-origin:top left;-webkit-user-select:none;-ms-user-select:none;user-select:none}.click-to-flip[data-v-0519f6d1]{position:absolute;width:50%;height:100%;top:0;-webkit-user-select:none;-ms-user-select:none;user-select:none}.click-to-flip.left[data-v-0519f6d1]{left:0}.click-to-flip.right[data-v-0519f6d1]{right:0}.bounding-box[data-v-0519f6d1]{position:absolute;-webkit-user-select:none;-ms-user-select:none;user-select:none}.page[data-v-0519f6d1]{position:absolute;-webkit-backface-visibility:hidden;backface-visibility:hidden}.polygon[data-v-0519f6d1]{position:absolute;top:0;left:0;background-repeat:no-repeat;-webkit-backface-visibility:hidden;backface-visibility:hidden;transform-origin:center left}.polygon.blank[data-v-0519f6d1]{background-color:#ddd}.polygon .lighting[data-v-0519f6d1]{width:100%;height:100%}", map: undefined, media: undefined }); inject("data-v-16acf8ed_0", { source: ".viewport[data-v-16acf8ed]{-webkit-overflow-scrolling:touch;width:100%;height:95%}.viewport.zoom[data-v-16acf8ed]{overflow:scroll}.viewport.zoom.drag-to-scroll[data-v-16acf8ed]{overflow:hidden}.book-container[data-v-16acf8ed]{position:relative;width:100%;height:95%;transform-origin:top left;-webkit-user-select:none;-ms-user-select:none;user-select:none}.click-to-flip[data-v-16acf8ed]{position:absolute;width:50%;height:100%;top:0;-webkit-user-select:none;-ms-user-select:none;user-select:none}.click-to-flip.left[data-v-16acf8ed]{left:0}.click-to-flip.right[data-v-16acf8ed]{right:0}.bounding-box[data-v-16acf8ed]{position:absolute;-webkit-user-select:none;-ms-user-select:none;user-select:none}.page[data-v-16acf8ed]{position:absolute;-webkit-backface-visibility:hidden;backface-visibility:hidden}.polygon[data-v-16acf8ed]{position:absolute;top:0;left:0;background-repeat:no-repeat;-webkit-backface-visibility:hidden;backface-visibility:hidden;transform-origin:center left}.polygon.blank[data-v-16acf8ed]{background-color:#ddd}.polygon .lighting[data-v-16acf8ed]{width:100%;height:100%}", map: undefined, media: undefined });
}; };
/* scoped */ /* scoped */
var __vue_scope_id__ = "data-v-0519f6d1"; var __vue_scope_id__ = "data-v-16acf8ed";
/* module identifier */ /* module identifier */
var __vue_module_identifier__ = undefined; var __vue_module_identifier__ = undefined;
/* functional template */ /* functional template */

File diff suppressed because one or more lines are too long

View file

@ -15,6 +15,10 @@ import Settings from "../views/settings.vue";
import Call from "../views/call.vue"; import Call from "../views/call.vue";
import ChildProfile from "../views/child_profile.vue"; import ChildProfile from "../views/child_profile.vue";
// Call Views
import CallLobby from "../views/call_views/Lobby.vue";
import CallBook from "../views/call_views/Book.vue";
const routes: RouteConfig[] = [ const routes: RouteConfig[] = [
/** Define Application Routes */ /** Define Application Routes */
{ {
@ -28,7 +32,11 @@ const routes: RouteConfig[] = [
}, },
{ {
path: "/call/:id", path: "/call/:id",
component: Call component: Call,
children: [
{ path: "", component: CallLobby, name: "lobby" },
{ path: "book", component: CallBook, name: "book" }
]
}, },
{ {
path: "/child/:id", path: "/child/:id",

View file

@ -1,11 +1,16 @@
import Vue from 'vue'; import Vue from 'vue';
import Vuex, { Store } from "vuex"; import Vuex, { Store } from "vuex";
import Services from '../services'; import Services from '../services';
import CallManager, { ECallEvents } from "./classes/call.manager";
import WebsocketService from './scripts/websocket.service';
import { createContext } from 'vm';
Vue.use(Vuex); Vue.use(Vuex);
const store = new Store({ const store = new Store({
strict: true, strict: true,
state: { state: {
user: null, inCall: false,
callManager: null as CallManager,
user: { name: 'loading...', is_admin: false, id: null },
notifications: [] notifications: []
}, },
getters: { getters: {
@ -14,6 +19,12 @@ const store = new Store({
}, },
notifications(state) { notifications(state) {
return state.notifications; return state.notifications;
},
callManager(state): CallManager {
return state.callManager;
},
inCall(state) {
return state.inCall;
} }
}, },
mutations: { mutations: {
@ -30,10 +41,18 @@ const store = new Store({
}, },
dismissNotification(state, noteId: number) { dismissNotification(state, noteId: number) {
state.notifications = state.notifications.filter(n => n.id != noteId); state.notifications = state.notifications.filter(n => n.id != noteId);
},
callEnded(state) {
state.callManager = null;
state.inCall = false;
},
connectToCall(state, options: { callId: number, ws: any }) {
state.callManager = new CallManager(options.ws, options.callId, state.user.id);
state.inCall = true;
} }
}, },
actions: { actions: {
getUser: async (ctx, userId?: number) => { async getUser(ctx, userId?: number) {
const user = await Services.ApiService.getUser(userId); const user = await Services.ApiService.getUser(userId);
ctx.commit('setUser', user); ctx.commit('setUser', user);
}, },
@ -42,6 +61,17 @@ const store = new Store({
}, },
dismissNotification(ctx, id: number) { dismissNotification(ctx, id: number) {
ctx.commit("dismissNotification", id); ctx.commit("dismissNotification", id);
},
callEnded(ctx) {
ctx.commit('callEnded');
},
async connectToCall(ctx, callId: string) {
if (!ctx.state.inCall) {
const ws = await WebsocketService.getInstance();
ctx.commit('connectToCall', { callId, ws });
return true;
}
return false;
} }
} }
}); });

View file

@ -1,89 +1,42 @@
<template> <template>
<div class="wrapper"> <div class="is-fullwidth">
<div v-if="loading"> <div v-if="loading">
<Loading /> <Loading />
</div> </div>
<div v-else class> <div v-else class>
<div class="floating-host is-flex" style="position:fixed;top:60px;left:100px"> <VideoStrip :localStream="localStream" :remoteStream="remoteStream" />
<h1 class="subtitle m-r-md">Host:</h1> <router-view></router-view>
<div class="me">
<figure class="image is-24x24">
<img
:src="isHost? user.avatar : callManager.peer.avatar"
class="is-rounded is-avatar"
@click="changeHost()"
/>
</figure>
</div>
</div>
<div class="video-strip m-b-md is-outlined">
<video
:src-object.prop.camel="localStream"
autoplay
playsinline
muted="true"
style="max-width:20%"
/>
<video :src-object.prop.camel="remoteStream" autoplay style="max-width:20%" />
</div>
<div class="is-flex">
<div class="go-left m-r-sm" style="display: flex; align-items: center;">
<button class="button is-outlined" :disabled="!isHost" @click="onLeftClicked()">
<i class="fa fa-fw fa-arrow-left"></i>
</button>
</div>
<flipbook
class="flipbook"
:pages="createPages(book)"
forwardDirection="left"
:zooms="null"
:enabled="isHost"
@flip-left-end="onFlip('left')"
@flip-right-end="onFlip('right')"
ref="flipbook"
v-slot="flipbook"
>
<div class="page-progress has-text-centered m-b-none">
<p>Page {{ flipbook.page }} of {{ flipbook.numPages }}</p>
</div>
</flipbook>
<div class="go-right m-l-sm" style="display: flex; align-items: center;">
<button class="button is-outlined" :disabled="!isHost" @click="onRightClicked()">
<i class="fa fa-fw fa-arrow-right"></i>
</button>
</div>
</div>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import Loading from "../../shared/components/Loading/Loading.vue"; import Loading from "../../shared/components/Loading/Loading.vue";
import WebsocketService from "../scripts/websocket.service"; import { ECallEvents } from "../classes/call.manager";
import CallManager, { ECallEvents } from "../classes/call.manager"; import VideoStrip from "./call_views/VideoStrip.vue";
import Flipbook from "../components/flipbook/flipbook.cjs.js";
import Services from "../../services/index"; import Services from "../../services/index";
import { mapActions, mapGetters } from "vuex"; import { mapActions, mapGetters } from "vuex";
export default { export default {
components: { components: {
Loading, Loading,
Flipbook VideoStrip
}, },
name: "Call", name: "Call",
mounted() { // mounted() {
const self = this; // const self = this;
setTimeout(() => { // setTimeout(() => {
self.isMounted = true; // self.isMounted = true;
}, 1000); // }, 1000);
}, // },
async created() { async created() {
this.loading = true; this.loading = true;
try { try {
const callId = Number(this.$route.params.id); const callId = Number(this.$route.params.id);
const ws = await WebsocketService.getInstance();
this.callManager = new CallManager(ws, callId, this.user.id); await this.connectToCall(callId);
this.callManager.on(ECallEvents.CLOSE, this.callEnded); this.callManager.on(ECallEvents.CLOSE, this.callEnded);
this.callManager.on(ECallEvents.ACTION_BOOK_FLIP, this.onRemoteFlip);
const success = await this.callManager.connectToCall({ const success = await this.callManager.connectToCall({
video: true, video: true,
audio: true audio: true
@ -110,71 +63,38 @@ export default {
async beforeDestroy() { async beforeDestroy() {
console.log("destroyed"); console.log("destroyed");
this.callManager.close(); this.callManager.close();
this.$store.dispatch("callEnded");
return true; return true;
}, },
methods: { methods: {
async setupCall(): Promise<boolean> { async setupCall(): Promise<boolean> {
return true; return true;
}, },
onFlip(direction: "left" | "right") {
if (this.isHost)
this.callManager.send(`book:action:flip-page`, { direction });
},
onLeftClicked() {
this.$refs.flipbook.flipLeft();
},
onRightClicked() {
this.$refs.flipbook.flipRight();
},
onRemoteFlip(payload) {
switch (payload.direction) {
case "left":
this.$refs.flipbook.flipLeft();
break;
case "right":
this.$refs.flipbook.flipRight();
break;
}
},
createPages(book) {
const pages = [null];
for (let i = 1; i < book.pages + 1; i++) {
pages.push(`/u/images/${i}.JPG`);
}
return pages;
},
callEnded(callId) { callEnded(callId) {
this.notify({ message: `Call #${callId} Ended` }); this.notify({ message: `Call #${callId} Ended` });
this.$router.push({ path: `/` }); this.$router.replace({ path: `/` });
}, },
onRemoteHostChanged(payload) { onRemoteHostChanged(payload) {
console.log("-----------"); console.log("-----------");
console.log(payload); console.log(payload);
this.peer = this.callManager.peer; this.peer = this.callManager.peer;
this.isHost = this.callManager.isHost;
}, },
changeHost() { changeHost() {
this.callManager.changeHost(); this.callManager.changeHost();
}, },
...mapActions(["notify"]) ...mapActions(["notify", "connectToCall"])
}, },
computed: { computed: {
...mapGetters(["user"]) ...mapGetters(["user", "callManager", "inCall"])
}, },
data() { data() {
return { return {
book: {
title: "Taylor",
pages: 34
},
loading: true, loading: true,
localStream: null, localStream: null,
remoteStream: null, remoteStream: null
callManager: null,
isHost: false,
// currentPage: 0, // currentPage: 0,
// totalPages: 34, // totalPages: 34,
isMounted: false // isMounted: false
}; };
}, },
beforeCreate: () => {} beforeCreate: () => {}

View file

@ -0,0 +1,97 @@
<template>
<div class="book-view">
<!-- <nav class="level">
<div class="level-left">
<div class="level-item">
<button class="button" @click="$router.back()">Back</button>
</div>
</div>
</nav>-->
<div class="is-flex">
<div class="go-left m-r-sm" style="display: flex; align-items: center;">
<button
class="button book-flip-buttons"
:disabled="!callManager.isHost"
@click="onLeftClicked()"
>
<i class="fa fa-fw fa-arrow-left"></i>
</button>
</div>
<flipbook
class="flipbook"
:pages="createPages(callManager.currentActivity)"
:forwardDirection="callManager.currentActivity.ltr ? 'right': 'left'"
:zooms="null"
:enabled="callManager.isHost"
@flip-left-end="onFlip('left')"
@flip-right-end="onFlip('right')"
ref="flipbook"
v-slot="flipbook"
>
<div class="page-progress has-text-centered m-b-none">
<p>Page {{ flipbook.page }} of {{ flipbook.numPages }}</p>
</div>
</flipbook>
<div class="go-right m-l-sm" style="display: flex; align-items: center;">
<button
class="button book-flip-buttons"
:disabled="!callManager.isHost"
@click="onRightClicked()"
>
<i class="fa fa-fw fa-arrow-right"></i>
</button>
</div>
</div>
</div>
</template>
<script lang="ts">
import Flipbook from "../../components/flipbook/flipbook.cjs.js";
import { ECallEvents } from "../../classes/call.manager";
import { mapActions, mapGetters } from "vuex";
export default {
name: "CallBook",
components: { Flipbook },
created() {
this.callManager.on(ECallEvents.ACTION_BOOK_FLIP, this.onRemoteFlip);
},
data() {
return {
localStream: null,
remoteStream: null
};
},
computed: {
...mapGetters(["user", "callManager"])
},
methods: {
onFlip(direction: "left" | "right") {
if (this.isHost)
this.callManager.send(`book:action:flip-page`, { direction });
},
onLeftClicked() {
this.$refs.flipbook.flipLeft();
},
onRightClicked() {
this.$refs.flipbook.flipRight();
},
onRemoteFlip(payload) {
switch (payload.direction) {
case "left":
this.$refs.flipbook.flipLeft();
break;
case "right":
this.$refs.flipbook.flipRight();
break;
}
},
createPages(book) {
const pages = [null];
for (let i = 1; i < book.pages + 1; i++) {
pages.push(`/u/books/${book.id}/page/${i}`);
}
return pages;
}
}
};
</script>

View file

@ -0,0 +1,43 @@
<template>
<div class="container">
<div class="Books">
<h2 class="subtitle">
<i class="fa fa-fw fa-book"></i> Select A Book
</h2>
<div class="is-flex m-b-md">
<div
class="book-thumb m-l-md"
v-for="(book, index) in callManager.books"
:key="index"
@click="goToBook(book, index)"
:disabled="!callManager.isHost"
>
<div class="book-cover">
<figure class="image is-2by3 m-a">
<img :src="`/u/books/${book.id}/page/1`" />
</figure>
</div>
<div class="book-text">
<div>{{book.title}}</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { mapGetters } from "vuex";
export default {
name: "CallLobby",
methods: {
goToBook(book, index) {
this.callManager.selectBook(index);
this.$router.replace({ name: "book" });
}
},
computed: {
...mapGetters(["callManager"])
}
};
</script>

View file

@ -0,0 +1,15 @@
<template>
<div class="container">
<div class="video-strip m-b-md">
<video :src-object.prop.camel="localStream" autoplay playsinline muted="true" />
<video :src-object.prop.camel="remoteStream" autoplay />
</div>
</div>
</template>
<script lang="ts">
export default {
name: "CallVideoStrip",
props: ["remoteStream", "localStream"]
};
</script>

View file

@ -1,5 +1,5 @@
<template> <template>
<div class="wrapper"> <div class="container">
<div class="loading" v-if="loading"> <div class="loading" v-if="loading">
<Loading /> <Loading />
</div> </div>
@ -73,7 +73,7 @@
</div> </div>
</nav> </nav>
<div class="parents"> <div class="parents">
<div class="columns"> <div class="is-flex">
<AvatarBadge <AvatarBadge
class="column" class="column"
v-for="parent in child.parents" v-for="parent in child.parents"

View file

@ -1,5 +1,5 @@
<template> <template>
<div class="wrapper"> <div class="container">
<div class="loading" v-if="loading"> <div class="loading" v-if="loading">
<Loading /> <Loading />
</div> </div>
@ -97,7 +97,7 @@
<i class="fa fa-fw fa-book"></i> My Books <i class="fa fa-fw fa-book"></i> My Books
</h2> </h2>
<div class="is-flex m-b-md"> <div class="is-flex m-b-md">
<div class="book m-l-md" v-for="i of [1, 2, 3, 4]" :key="i"> <div class="book-thumb m-l-md" v-for="i of [1, 2, 3, 4]" :key="i">
<div class="book-cover"> <div class="book-cover">
<figure class="image is-2by3 m-a"> <figure class="image is-2by3 m-a">
<img <img

View file

@ -1,5 +1,5 @@
<template> <template>
<div class="wrapper"> <div class="container">
<div class="loading" v-if="loading"> <div class="loading" v-if="loading">
<Loading /> <Loading />
</div> </div>

View file

@ -10,7 +10,12 @@
</div> </div>
<aside class="menu is-primary sidebar-menu"> <aside class="menu is-primary sidebar-menu">
<ul class="menu-list"> <ul class="menu-list">
<li v-for="item in menu" class="m-t-md m-b-md" @click="onItemClicked(item)"> <li
v-for="item in menu"
class="m-t-md m-b-md"
@click="onItemClicked(item)"
:key="item.text"
>
<router-link <router-link
active-class="is-active" active-class="is-active"
v-if="item.isRouterLink" v-if="item.isRouterLink"

View file

@ -0,0 +1,115 @@
<template>
<nav class="navbar" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
<router-link to="/" class="navbar-item" exact v-if="!inCall">
<strong>Seepur</strong>
</router-link>
<!-- <a class="navbar-item" href="/">
</a>-->
<a
id="menu-button"
role="button"
class="navbar-burger burger"
@click="showMenu=!showMenu"
aria-label="menu"
aria-expanded="false"
data-target="navbarBasicExample"
>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</div>
<div id="nav-menu" :class="['navbar-menu', {'is-active':showMenu}]">
<div class="navbar-start">
<!-- Normal Nav -->
<router-link active-class="is-active" to="/" class="navbar-item" exact v-if="!inCall">Home</router-link>
<router-link
active-class="is-active"
to="/about"
class="navbar-item"
exact
v-if="!inCall"
>About</router-link>
<!-- In call Nav -->
<div class="navbar-item" v-if="inCall">
<div class="field is-grouped">
<p class="control">
<router-link
active-class="is-active"
to="/"
class="button is-danger"
exact
replace
v-if="inCall"
>
<i class="fa fa-fw fa-times-circle-o"></i> End Call
</router-link>
</p>
<p class="control">
<router-link
:to="{path:'..'}"
class="button is-info"
append
replace
v-if="inCall && $route.name==='book'"
>
<i class="fa fa-fw fa-arrow-circle-o-left"></i> Back
</router-link>
</p>
</div>
</div>
</div>
<div class="navbar-end">
<div class="navbar-item has-dropdown is-hoverable is-dark" v-if="!inCall">
<a class="navbar-link">{{user.name}}</a>
<div class="navbar-dropdown">
<router-link active-class="is-active" to="/settings" class="navbar-item" exact>Settings</router-link>
<a class="navbar-item" href="/logout">Logout</a>
<hr class="navbar-divider" v-if="user.is_admin" />
<a class="navbar-item" href="/admin/" v-if="user.is_admin">Admin Settigns</a>
</div>
</div>
<div class="navbar-item" v-if="inCall">
<p class="control">
<button class="button" @click="changeHost()">
<i class="fa fa-fw fa-refresh"></i> Change Host
</button>
</p>
</div>
<div v-if="inCall" class="navbar-item">Current Host: {{host.name}}</div>
</div>
</div>
</nav>
</template>
<script lang="ts">
import { mapGetters, mapActions } from "vuex";
export default {
data() {
return {
showMenu: false
};
},
computed: {
host() {
if (this.inCall) {
if (this.callManager.isHost) return this.user;
return this.callManager.peer;
}
return null;
},
...mapGetters(["user", "inCall", "callManager"])
},
methods: {
changeHost() {
this.callManager.changeHost();
},
...mapActions([])
}
};
</script>

View file

@ -14,7 +14,7 @@
</head> </head>
<body> <body>
@!component('components.nav.nav', isLanding = false, auth=auth) {{-- @!component('components.nav.nav', isLanding = false, auth=auth) --}}
@!section('hero') @!section('hero')
<div class=""> <div class="">

View file

@ -42,6 +42,7 @@ const globalMiddleware =
auth: 'Adonis/Middleware/Auth', auth: 'Adonis/Middleware/Auth',
guest: 'Adonis/Middleware/AllowGuestOnly', guest: 'Adonis/Middleware/AllowGuestOnly',
adminAuth: 'App/Middleware/AdminAuth', adminAuth: 'App/Middleware/AdminAuth',
BookPageAuth: 'App/Middleware/BookPageAuth'
} }
/* /*

View file

@ -44,6 +44,12 @@ Route
.prefix('api/v1/client') .prefix('api/v1/client')
.middleware(['auth']); .middleware(['auth']);
/**
* Private book assets
*/
Route.get('/u/books/:bookId/page/:pageNumber', 'BookApiController.getPage')
.middleware(['auth', 'BookPageAuth']);
/* /*
/ Pubic CDN Images / Pubic CDN Images
*/ */