[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);
else
return {
ERROR: 'no file'
code: 404, message: 'no file'
}
}
}

View File

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

View File

@ -3,6 +3,7 @@
/** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */
const Model = use('Model')
const User = use('App/Models/User');
const Book = use('App/Models/Book');
class Call extends Model {
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 {
return await Drive.get(filename)
} catch (e) {
console.error(e);
return null;
}
}

View File

@ -8,6 +8,7 @@ class BookSchema extends Schema {
this.create('books', (table) => {
table.increments()
table.bigInteger('user_id');
table.string('title', 80).notNullable();
table.integer('pages').notNullable();
table.string('book_folder').notNullable();
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;
padding: 3rem 1.5rem 6rem; }
.navbar {
background: #fafafa; }
.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-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 {
background: rgba(0, 0, 0, 0.7);
/* Old browsers */
@ -6846,13 +6860,19 @@ label.panel-block {
.is-fullheight {
min-height: calc(100vh - ( 3.25rem ));
max-height: calc(100vh - ( 3.25rem ));
height: calc(100vh - ( 3.25rem ));
display: flex;
flex-direction: row;
justify-content: stretch; }
height: calc(100vh - ( 3.25rem )); }
.is-fullheight .column {
overflow-y: auto; }
.is-fullwidth {
width: 100vw;
overflow: hidden; }
.app-content {
display: flex;
flex-direction: row;
justify-content: center; }
.avatar-badge-image {
display: table;
margin: auto; }
@ -6917,16 +6937,37 @@ label.panel-block {
.flipbook {
width: 100%;
height: 60vh; }
height: 75vh;
overflow-y: visible; }
.book-flip-buttons {
height: 100%; }
.video-strip {
display: flex;
justify-content: center;
align-content: center; }
align-content: center;
max-height: 15vh; }
video {
margin-left: 5px;
margin-right: 5px;
background-color: rgba(56, 181, 187, 0.19);
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 "../../node_modules/bulma/bulma.sass";
// @import '../../node_modules/animate.css/source/_base.css';
.navbar {
background:#fafafa;
}
.hero-bg{
@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 {
.navbar.darken {
@include linearGradient(rgba(0, 0, 0, .7), rgba(0, 0, 0, 0));
}
// .navbar-item{
.navbar.darken {
@include linearGradient(rgba(0, 0, 0, .7), rgba(0, 0, 0, 0));
}
// .navbar-item{
// color: $primary;
// }
}
.has-inner-shadow-bottom{
box-shadow: inset 0px -10px 15px -11px rgba(0,0,0,0.75);
}
.has-inner-shadow-top{
box-shadow: inset 0px 10px 15px -11px rgba(0,0,0,0.75);
}
}
.has-inner-shadow-bottom{
box-shadow: inset 0px -10px 15px -11px rgba(0,0,0,0.75);
}
.has-inner-shadow-top{
box-shadow: inset 0px 10px 15px -11px rgba(0,0,0,0.75);
}
.has-shadow-top{
box-shadow: 0px 10px 15px -11px rgba(0,0,0,0.75);
}
@ -32,49 +52,49 @@ $marginKey: 'm';
$paddingKey: 'p';
$separator: '-';
$sizes: (
('none', 0),
('xxs', 0.75),
('xs', 0.25),
('sm', 0.5),
('md', 1),
('lg', 2),
('xl', 4),
('xxl', 8),
);
$positions: (
('none', 0),
('xxs', 0.75),
('xs', 0.25),
('sm', 0.5),
('md', 1),
('lg', 2),
('xl', 4),
('xxl', 8),
);
$positions: (
('t', 'top'),
('r', 'right'),
('b', 'bottom'),
('l', 'left')
);
);
@function sizeValue($key, $value) {
@return if($key == 'none', 0, $value + $sizeUnit);
}
@function sizeValue($key, $value) {
@return if($key == 'none', 0, $value + $sizeUnit);
}
@each $size in $sizes {
$sizeKey: nth($size, 1);
$sizeValue: nth($size, 2);
.#{$marginKey}#{$separator}#{$sizeKey} {
@each $size in $sizes {
$sizeKey: nth($size, 1);
$sizeValue: nth($size, 2);
.#{$marginKey}#{$separator}#{$sizeKey} {
margin: sizeValue($sizeKey, $sizeValue);
}
.#{$paddingKey}#{$separator}#{$sizeKey} {
}
.#{$paddingKey}#{$separator}#{$sizeKey} {
padding: sizeValue($sizeKey, $sizeValue);
}
@each $position in $positions {
}
@each $position in $positions {
$posKey: nth($position, 1);
$posValue: nth($position, 2);
.#{$marginKey}#{$separator}#{$posKey}#{$separator}#{$sizeKey} {
margin-#{$posValue}: sizeValue($sizeKey, $sizeValue);
margin-#{$posValue}: sizeValue($sizeKey, $sizeValue);
}
.#{$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{
margin-bottom: auto;
}
@ -84,48 +104,24 @@ $positions: (
.m-r-a{
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{
// margin: auto;
flex-direction: column;
max-width: 5em;
width: 5em;
height: 100%;
// background-color:rgba(127, 88, 145, 0.7);
// color: $beige-lighter;
border-right: thin #ccc solid;
}
.menu-titles{
margin-top: -2em;
}
.is-sidebar {
// transform: translate(15%, 0);
// background-color: rgba(0, 0, 0, .3);
}
.sidebar-menu{
position: absolute;
bottom: 25px;
left: 1em;
}
.sideway-text{
// transform: translate(0, -50%);
// display: inline;
// margin: 0 0;
}
.sideway-letter{
// width: 50%;
// height: 2em;
transform: rotate(-90deg);
margin: -.8em auto;
}
@ -134,14 +130,20 @@ $positions: (
min-height: calc(100vh - ( #{$navbar-height} ) );
max-height: calc(100vh - ( #{$navbar-height} ) );
height: calc(100vh - ( #{$navbar-height} ) );
display: flex;
flex-direction: row;
justify-content: stretch;
.column{
overflow-y: auto;
}
}
.is-fullwidth{
width: 100vw;
overflow: hidden;
}
.app-content{
display: flex;
flex-direction: row;
justify-content: center;
}
.avatar-badge-image{
display: table;
margin: auto;
@ -223,18 +225,42 @@ $positions: (
// TODO: Remove this - make generic
.flipbook {
width: 100%;
height: 60vh;
height: 75vh;
overflow-y: visible;
}
.book-flip-buttons{
height: 100%;
}
.video-strip{
display: flex;
justify-content: center;
align-content:center;
max-height: 15vh;
}
video{
margin-left: 5px;
margin-right: 5px;
background-color:rgba(56, 181, 187, 0.19);
border-radius: 15px;
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>
<div class="app">
<!-- <Header :appName="appName" /> -->
<TopNavbar />
<div class="loading" v-if="loading">
<Loading />
</div>
@ -13,15 +14,10 @@
@onClose="onNotificationClose(notification)"
/>
</div>
<div class="columns m-t-xs is-fullheight">
<div class="column sidebar">
<SideBar :title="appName" :menu="menu" :appName="appName" />
<div class="app-content m-t-xs is-fullheight">
<div class="application">
<router-view></router-view>
</div>
<section class="section column app-content">
<div class="container">
<router-view></router-view>
</div>
</section>
</div>
</div>
</div>
@ -33,42 +29,20 @@ import Services from "../services/index";
import Notification from "../shared/components/Notification.vue";
import { mapActions, mapGetters } from "vuex";
import Loading from "../shared/components/Loading/Loading.vue";
import TopNavbar from "../shared/components/TopNavbar.vue";
import WebsocketService from "./scripts/websocket.service";
// 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();
export default {
name: "App",
// router: AppRouter,
components: {
SideBar,
Header,
Notification,
Loading
Loading,
TopNavbar
},
async created() {
await this.getUser();
@ -90,7 +64,6 @@ export default {
data() {
return {
appName: "Seepur",
menu,
loading: true,
ws: null
};
@ -104,7 +77,7 @@ export default {
message: `New call from ${payload.child.name}`,
level: "success"
});
this.$router.push({ path: `/call/${payload.callId}` });
this.$router.replace({ path: `/call/${payload.callId}` });
},
onNotificationClose(notification) {
this.dismissNotification(notification.id);

View File

@ -6,12 +6,13 @@ export default class CallManager {
private localStream: MediaStream;
private remoteStream: MediaStream;
private signalingChannel;
private needToAddStream: boolean = true;
private emitter = new EventEmitter();
private pc: RTCPeerConnection;
public child;
public peer = { avatar: '' };
public isHost = false;
public books = [];
public currentActivity = null;
constructor(private ws: WebSocketService, private callId: number, private userId: number) {
this.inCall = false;
this.peerId = null;
@ -60,10 +61,11 @@ export default class CallManager {
...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(payload);
this.peerId = payload.peerId;
this.books = payload.books;
this.isHost = this.peerId === payload.hostId;
this.pc = new RTCPeerConnection({ iceServers: payload.iceServers });
this.child = payload.child;
@ -84,10 +86,11 @@ export default class CallManager {
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(payload);
this.peerId = payload.peerId;
this.books = payload.books;
this.isHost = this.peerId === payload.hostId;
this.pc = new RTCPeerConnection({ iceServers: payload.iceServers });
this.child = payload.child;
@ -102,6 +105,10 @@ export default class CallManager {
return true;
}
public selectBook(index: number) {
this.currentActivity = this.books[index];
}
setupPeerConnectionListeners() {
this.pc.addEventListener("icecandidate", this.onLocalIce.bind(this));
this.pc.addEventListener('connectionstatechange', event => {
@ -174,10 +181,7 @@ export default class CallManager {
};
changeHost() {
this.isHost = !this.isHost;
const hostId = this.isHost ? this.userId : this.peerId;
this.emit(ECallEvents.CALL_HOST_CHANGED, { hostId });
this.send('call:host:changed', { hostId });
this.send('call:host:changed', {});
}
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:{
zoom: _vm.zooming || _vm.zoom > 1,
'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',
height: _vm.pageHeight + 'px',
left: _vm.xMargin + 'px',
@ -1244,11 +1244,11 @@ var __vue_staticRenderFns__ = [];
/* style */
var __vue_inject_styles__ = function (inject) {
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 */
var __vue_scope_id__ = "data-v-0519f6d1";
var __vue_scope_id__ = "data-v-16acf8ed";
/* module identifier */
var __vue_module_identifier__ = undefined;
/* 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:{
zoom: _vm.zooming || _vm.zoom > 1,
'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',
height: _vm.pageHeight + 'px',
left: _vm.xMargin + 'px',
@ -1242,11 +1242,11 @@ var __vue_staticRenderFns__ = [];
/* style */
var __vue_inject_styles__ = function (inject) {
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 */
var __vue_scope_id__ = "data-v-0519f6d1";
var __vue_scope_id__ = "data-v-16acf8ed";
/* module identifier */
var __vue_module_identifier__ = undefined;
/* functional template */

View File

@ -1388,7 +1388,7 @@
}),_vm._v(" "),_c('div',{ref:"viewport",staticClass:"viewport",class:{
zoom: _vm.zooming || _vm.zoom > 1,
'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',
height: _vm.pageHeight + 'px',
left: _vm.xMargin + 'px',
@ -1425,11 +1425,11 @@
/* style */
var __vue_inject_styles__ = function (inject) {
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 */
var __vue_scope_id__ = "data-v-0519f6d1";
var __vue_scope_id__ = "data-v-16acf8ed";
/* module identifier */
var __vue_module_identifier__ = undefined;
/* 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 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[] = [
/** Define Application Routes */
{
@ -28,7 +32,11 @@ const routes: RouteConfig[] = [
},
{
path: "/call/:id",
component: Call
component: Call,
children: [
{ path: "", component: CallLobby, name: "lobby" },
{ path: "book", component: CallBook, name: "book" }
]
},
{
path: "/child/:id",

View File

@ -1,11 +1,16 @@
import Vue from 'vue';
import Vuex, { Store } from "vuex";
import Services from '../services';
import CallManager, { ECallEvents } from "./classes/call.manager";
import WebsocketService from './scripts/websocket.service';
import { createContext } from 'vm';
Vue.use(Vuex);
const store = new Store({
strict: true,
state: {
user: null,
inCall: false,
callManager: null as CallManager,
user: { name: 'loading...', is_admin: false, id: null },
notifications: []
},
getters: {
@ -14,6 +19,12 @@ const store = new Store({
},
notifications(state) {
return state.notifications;
},
callManager(state): CallManager {
return state.callManager;
},
inCall(state) {
return state.inCall;
}
},
mutations: {
@ -30,10 +41,18 @@ const store = new Store({
},
dismissNotification(state, noteId: number) {
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: {
getUser: async (ctx, userId?: number) => {
async getUser(ctx, userId?: number) {
const user = await Services.ApiService.getUser(userId);
ctx.commit('setUser', user);
},
@ -42,6 +61,17 @@ const store = new Store({
},
dismissNotification(ctx, id: number) {
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>
<div class="wrapper">
<div class="is-fullwidth">
<div v-if="loading">
<Loading />
</div>
<div v-else class>
<div class="floating-host is-flex" style="position:fixed;top:60px;left:100px">
<h1 class="subtitle m-r-md">Host:</h1>
<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>
<VideoStrip :localStream="localStream" :remoteStream="remoteStream" />
<router-view></router-view>
</div>
</div>
</template>
<script lang="ts">
import Loading from "../../shared/components/Loading/Loading.vue";
import WebsocketService from "../scripts/websocket.service";
import CallManager, { ECallEvents } from "../classes/call.manager";
import Flipbook from "../components/flipbook/flipbook.cjs.js";
import { ECallEvents } from "../classes/call.manager";
import VideoStrip from "./call_views/VideoStrip.vue";
import Services from "../../services/index";
import { mapActions, mapGetters } from "vuex";
export default {
components: {
Loading,
Flipbook
VideoStrip
},
name: "Call",
mounted() {
const self = this;
setTimeout(() => {
self.isMounted = true;
}, 1000);
},
// mounted() {
// const self = this;
// setTimeout(() => {
// self.isMounted = true;
// }, 1000);
// },
async created() {
this.loading = true;
try {
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.ACTION_BOOK_FLIP, this.onRemoteFlip);
const success = await this.callManager.connectToCall({
video: true,
audio: true
@ -110,71 +63,38 @@ export default {
async beforeDestroy() {
console.log("destroyed");
this.callManager.close();
this.$store.dispatch("callEnded");
return true;
},
methods: {
async setupCall(): Promise<boolean> {
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) {
this.notify({ message: `Call #${callId} Ended` });
this.$router.push({ path: `/` });
this.$router.replace({ path: `/` });
},
onRemoteHostChanged(payload) {
console.log("-----------");
console.log(payload);
this.peer = this.callManager.peer;
this.isHost = this.callManager.isHost;
},
changeHost() {
this.callManager.changeHost();
},
...mapActions(["notify"])
...mapActions(["notify", "connectToCall"])
},
computed: {
...mapGetters(["user"])
...mapGetters(["user", "callManager", "inCall"])
},
data() {
return {
book: {
title: "Taylor",
pages: 34
},
loading: true,
localStream: null,
remoteStream: null,
callManager: null,
isHost: false,
remoteStream: null
// currentPage: 0,
// totalPages: 34,
isMounted: false
// isMounted: false
};
},
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>
<div class="wrapper">
<div class="container">
<div class="loading" v-if="loading">
<Loading />
</div>
@ -73,7 +73,7 @@
</div>
</nav>
<div class="parents">
<div class="columns">
<div class="is-flex">
<AvatarBadge
class="column"
v-for="parent in child.parents"

View File

@ -1,5 +1,5 @@
<template>
<div class="wrapper">
<div class="container">
<div class="loading" v-if="loading">
<Loading />
</div>
@ -97,7 +97,7 @@
<i class="fa fa-fw fa-book"></i> My Books
</h2>
<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">
<figure class="image is-2by3 m-a">
<img

View File

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

View File

@ -10,7 +10,12 @@
</div>
<aside class="menu is-primary sidebar-menu">
<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
active-class="is-active"
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>
<body>
@!component('components.nav.nav', isLanding = false, auth=auth)
{{-- @!component('components.nav.nav', isLanding = false, auth=auth) --}}
@!section('hero')
<div class="">

View File

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

View File

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