[WIP] Basic lobby and multibook support. Call is not working ATM. Need to fix signaling
This commit is contained in:
parent
04d7211de1
commit
0e2fa21511
35 changed files with 634 additions and 257 deletions
17
app/Controllers/Http/BookApiController.js
Normal file
17
app/Controllers/Http/BookApiController.js
Normal 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
|
|
@ -8,7 +8,7 @@ class CdnController {
|
|||
response.send(file);
|
||||
else
|
||||
return {
|
||||
ERROR: 'no file'
|
||||
code: 404, message: 'no 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;
|
||||
}
|
||||
}
|
||||
|
|
40
app/Middleware/BookPageAuth.js
Normal file
40
app/Middleware/BookPageAuth.js
Normal 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
|
|
@ -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');
|
||||
|
|
|
@ -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
18
app/Utils/CallUtils.js
Normal 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;
|
|
@ -15,6 +15,7 @@ class FileUtils {
|
|||
try {
|
||||
return await Drive.get(filename)
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
@ -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); }
|
||||
|
|
|
@ -5,10 +5,30 @@
|
|||
@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 {
|
||||
|
@ -17,13 +37,13 @@
|
|||
// .navbar-item{
|
||||
// color: $primary;
|
||||
// }
|
||||
}
|
||||
.has-inner-shadow-bottom{
|
||||
}
|
||||
.has-inner-shadow-bottom{
|
||||
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);
|
||||
}
|
||||
}
|
||||
.has-shadow-top{
|
||||
box-shadow: 0px 10px 15px -11px rgba(0,0,0,0.75);
|
||||
}
|
||||
|
@ -40,19 +60,19 @@ $sizes: (
|
|||
('lg', 2),
|
||||
('xl', 4),
|
||||
('xxl', 8),
|
||||
);
|
||||
$positions: (
|
||||
);
|
||||
$positions: (
|
||||
('t', 'top'),
|
||||
('r', 'right'),
|
||||
('b', 'bottom'),
|
||||
('l', 'left')
|
||||
);
|
||||
);
|
||||
|
||||
@function sizeValue($key, $value) {
|
||||
@function sizeValue($key, $value) {
|
||||
@return if($key == 'none', 0, $value + $sizeUnit);
|
||||
}
|
||||
}
|
||||
|
||||
@each $size in $sizes {
|
||||
@each $size in $sizes {
|
||||
$sizeKey: nth($size, 1);
|
||||
$sizeValue: nth($size, 2);
|
||||
.#{$marginKey}#{$separator}#{$sizeKey} {
|
||||
|
@ -71,10 +91,10 @@ $positions: (
|
|||
padding-#{$posValue}: sizeValue($sizeKey, $sizeValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
.m-t-a{
|
||||
}
|
||||
.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);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
<section class="section column app-content">
|
||||
<div class="container">
|
||||
<div class="app-content m-t-xs is-fullheight">
|
||||
<div class="application">
|
||||
<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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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: () => {}
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="wrapper">
|
||||
<div class="container">
|
||||
<div class="loading" v-if="loading">
|
||||
<Loading />
|
||||
</div>
|
||||
|
|
|
@ -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"
|
||||
|
|
115
resources/scripts/applications/shared/components/TopNavbar.vue
Normal file
115
resources/scripts/applications/shared/components/TopNavbar.vue
Normal 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>
|
||||
|
|
@ -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="">
|
||||
|
|
|
@ -42,6 +42,7 @@ const globalMiddleware =
|
|||
auth: 'Adonis/Middleware/Auth',
|
||||
guest: 'Adonis/Middleware/AllowGuestOnly',
|
||||
adminAuth: 'App/Middleware/AdminAuth',
|
||||
BookPageAuth: 'App/Middleware/BookPageAuth'
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
Reference in a new issue