Compare commits

...

10 commits

21 changed files with 1296 additions and 68 deletions

View file

@ -236,6 +236,31 @@ class ClientApiController {
return error;
}
}
async createBook({request, response, auth}) {
// TODO: Validate input!
const user = auth.user;
const bookPayload = request.body;
// console.log('BookPages')
const bookHash = uuidv4();
const bookDrivePromises = [];
const bookRelativePath = `uploads/${bookHash}`;
const bookAbsolutePath = `books/${bookRelativePath}`;
for (let i = 0; i < bookPayload.pages.length; i++) {
const filePayload = bookPayload.pages[i];
bookDrivePromises.push(FileUtils.saveBase64File(
filePayload, `${bookAbsolutePath}/${i + 1}.jpg`));
};
await Promise.all(bookDrivePromises);
const book = await Book.create({
user_id: user.id,
title: bookPayload.title,
pages: bookPayload.pages.length,
book_folder: bookRelativePath,
ltr: bookPayload.ltr
});
return {code: 0, data: book};
}
}
module.exports = ClientApiController

View file

@ -0,0 +1,33 @@
'use strict'
/** @typedef {import('@adonisjs/framework/src/Request')} Request */
/** @typedef {import('@adonisjs/framework/src/Response')} Response */
/** @typedef {import('@adonisjs/framework/src/View')} View */
class BookCallPageAuth {
/**
* @param {object} ctx
* @param {Request} ctx.request
* @param {Function} next
*/
async handle(ctx, next) {
const {request, auth, response, book, call} = ctx;
// call next to advance the request
const user = auth.user;
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 if (call.parent_id === user.id || call.guest_id === user.id) {
await next();
} else {
response.status(403);
response.send({code: 403, message: 'Book is private'});
}
} else {
await next();
}
}
}
module.exports = BookCallPageAuth

View file

@ -2,8 +2,7 @@
/** @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
@ -11,7 +10,7 @@ class BookPageAuth {
* @param {Function} next
*/
async handle(ctx, next) {
const {request, auth, response, book, call} = ctx;
const {request, auth, response, book} = ctx;
// call next to advance the request
const user = auth.user;
if (book.user_id) {
@ -19,8 +18,6 @@ class BookPageAuth {
// user
if (book.user_id === user.id) {
await next();
} else if (call.parent_id === user.id || call.guest_id === user.id) {
await next();
} else {
response.status(403);
response.send({code: 403, message: 'Book is private'});

View file

@ -2,11 +2,12 @@ const Drive = use('Drive');
class FileUtils {
static async saveBase64File(base64Str) {
static async saveBase64File(base64Str, _fileName = null) {
console.log(base64Str.length);
const parsed = parseBase64(base64Str);
const fileName =
const fileName = _fileName ||
`${Date.now()}-${Math.random() * 1000}.${parsed.extension}`;
console.log(fileName);
const file = await Drive.put(fileName, parsed.data);
return {fileName, file};
}

View file

@ -1,38 +1,81 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="416.217px" height="320.271px" viewBox="0 0 416.217 320.271" enable-background="new 0 0 416.217 320.271"
xml:space="preserve">
<path fill="none" d="M394.117,52.481c-7.972-2.006-13.092,8.789-6.313,12.514c2.938,1.615,16.261-1.004,19.756-0.984
c13.258,0.076,29.066-2.875,42.151-0.119c1.609-4.211,1.439-8.369-1.647-11.504c-14.189,6.227-40.318,0.645-54.844,0.094"/>
<path fill="#1D1B88" d="M37.057,127.323c19.387-9.344,40.18-9.455,61.592-1.916c13.055,4.596,23.707,13.828,36.516,18.154
c14.568,4.918,28.163,9.368,43.853,10.801c33.112,3.021,64.396,11.681,98.155,8.614c31.721-2.879,52.089-24.647,53.298-58.286
c0.594-16.504-8.058-31.07-6.314-48.687c1.783-18.01,7.156-25.946,19.578-37.653c5.897,8.243,10.637,17.251,14.088,26.606
c3.865,10.486-0.469,17.875,0.789,29.094c6.428-28.464,21.398-30.406,45.775-36.254c2.444,13.24-0.197,34.666-6.396,46.003
c-7.09,12.964-17.443,21.88-29.76,28.632c-16.209,8.888-25.014,13.044-33.404,30.286c-11.938,24.54-25.392,49.821-45.306,68.679
c-18.49,17.508-52.829,32.369-77.049,34.816c-32.284,3.262-64.088,7.459-93.959-5.669c-25.17-11.062-44.333-27.558-61.878-49.198
c-4.621-5.701-6.869-12.688-11.482-18.369c-6.27-7.721-11.066-5.008-18.303-9.153c-12.646-7.247,1.549-28.844,10.208-35.418"/>
<path fill="#EAB0D4" d="M202.008,159.699c-0.453,3.444-9.512,17.323-13.782,19.231c-9.204,4.111-19.131-5.356-28.859-2.859
c-0.416,6.123,11.912,9.218,16.833,12.529c8.162,5.492,9.108,6.719,18.266,3.275c8.433-3.171,14.195-7.451,23.204-7.35
c8.942,0.103,15.044,0.115,22.972-1.111c15.088-2.335,38.19-8.296,52.227-2.977c13.217,5.012,21.434,16.401,36.277,19.481
c15.463,3.207,31.297,10.226,47.059,10.402c0.525-1.664,0.492-3.628,0.285-5.08c-6.301-1.664-11.668-3.777-17.117-7.433
c-1.94-1.302-1.83-5.287-4.885-6.824c-1.937-0.975-7.66,0.775-10.437,0.045c-10.049-2.648-28.935-15.67-30.334-25.93
c-1.363-10.001,8.255-30.093,11.693-39.742c5.719-16.053,6.162-24.084-0.387-40.031c-6.857,0.03-6.359,21.592-8.203,27.875
c-2.6,8.861-5.125,19.344-12.271,25.528c-19.594-12.017-35.82,7.034-52.429,5.838c-12.344-0.89-13.365,1.892-23.172,8.433
c-7.848,5.234-18.899,6.94-26.94,9.938"/>
<path fill="#EDECDC" d="M21.397,158.338c0,36.892-5.496,68.336,21.169,97.22c9.782,10.596,20.312,23.77,31.315,33.293
c12.66,10.959,28.458,12.062,43.483,17.739c28.911,10.922,71.75,5.123,100.045-5.882c16.802-6.533,27.143-10.104,39.146-24.611
c9.9-11.968,22.979-18.481,31.287-32.391c5.867-9.82,22.979-37.454,18.613-48.248c-11.146,8.85-18.016,20.773-31.138,28.797
c-15.161,9.27-29.567,18.252-47.212,21.878c-33.11,6.804-67.33,6.304-99.181-3.477c-11.869-3.646-24.959-4.321-35.498-12.154
c-9.404-6.987-18.122-15.58-25.811-24.883c-8.288-10.027-11.969-23.651-19.105-32.127c-5.23-6.212-26.525-9.255-25.028-15.154"/>
<path fill="#FFEEF2" d="M146.676,164.821c10.386,9.195,24.997,14.287,38.366,17.131c7.076-6.071,27.167-21.166,4.409-22.615
c-2.687-0.17-6.324,3.033-9.37,3.319c-4.036,0.379-7.785-1.337-11.224-1.343c-5.205-0.008-16.47-0.447-20.743,3.772
c0.902,1.367,1.76,2.034,2.739,2.976"/>
<path fill="#F4F0A2" d="M157.116,126.721c-1.388-3.18-13.381-0.909-17.772,4.326c-2.879,3.43-6.806,19.18-6.312,22.638
c1.107,7.775,9.504,13.328,16.778,13.019c3.578-0.152,7.02-4.014,9.604-4.107c5.088-0.185,5.103,3.532,9.42,3.859
c7.792,0.59,11.137-6.646,18.586-4.342c8.555,2.645,7.493,8.214,17.95,4.826c4.648-1.506,16.479-5.07,19.89-8.589
c8.562-8.832-1.181-11.038-3.178-18.907c-2.157-8.493,4.019-12.391-4.18-19.447c-5.864-5.047-15.099-7.182-22.385-6.511
c-13.268,1.222-26.209,11.132-38.399,14.315"/>
<ellipse fill="#E6DDE1" cx="96.131" cy="179.692" rx="3.597" ry="3.292"/>
</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
sodipodi:docname="whale.svg"
xml:space="preserve"
enable-background="new 0 0 416.217 320.271"
viewBox="0 0 416.217 320.271"
height="320.271px"
width="416.217px"
y="0px"
x="0px"
id="Layer_1"
version="1.1"><metadata
id="metadata25"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
id="defs23" /><sodipodi:namedview
inkscape:current-layer="Layer_1"
inkscape:window-maximized="1"
inkscape:window-y="0"
inkscape:window-x="0"
inkscape:cy="160.1355"
inkscape:cx="208.10851"
inkscape:zoom="2.8663226"
showgrid="false"
id="namedview21"
inkscape:window-height="1136"
inkscape:window-width="1920"
inkscape:pageshadow="2"
inkscape:pageopacity="0"
guidetolerance="10"
gridtolerance="10"
objecttolerance="10"
borderopacity="1"
bordercolor="#666666"
pagecolor="#ffffff" />
<path
id="path2"
d="M12.986,129.484c-2.773,36.785-10.619,67.726,13.799,98.534c8.958,11.302,18.468,25.23,28.724,35.555 c11.8,11.88,27.47,14.167,42.025,20.957c28.008,13.065,71.161,10.505,100.205,1.657c17.246-5.25,27.826-8.032,40.886-21.597 c10.771-11.19,24.304-16.7,33.633-29.946c6.59-9.352,25.73-35.62,22.189-46.713c-11.781,7.987-19.527,19.36-33.217,26.374 c-15.814,8.104-30.854,15.978-48.723,18.267c-33.527,4.295-67.614,1.224-98.639-10.925c-11.561-4.527-24.563-6.187-34.483-14.789 c-8.853-7.675-16.899-16.899-23.867-26.754c-7.51-10.62-10.157-24.481-16.635-33.471c-4.749-6.588-25.754-11.223-23.818-16.992"
fill="#F7F4E2" />
<path
id="path4"
d="M394.737,67.927c-7.628-3.066-14.161,6.937-7.948,11.544c2.692,1.998,16.247,1.206,19.707,1.699 c13.126,1.868,29.188,1.084,41.78,5.585c2.163-3.955,2.558-8.097-0.076-11.621c-14.9,4.25-40.034-4.817-54.352-7.328"
fill="none" />
<ellipse
id="ellipse6"
ry="3.597"
rx="3.292"
cy="153.644"
cx="82.278"
fill="#FCFCFC"
transform="matrix(0.1353 -0.9908 0.9908 0.1353 -81.0843 214.3818)" />
<path
id="path10"
d="M29.667,101.408c20.163-7.524,40.878-5.726,61.508,3.748c12.578,5.775,22.337,15.946,34.695,21.43 c14.054,6.235,27.184,11.915,42.675,14.781c32.696,6.048,63.052,17.544,96.95,17.591c31.852,0.045,54.133-19.761,58.424-53.146 c2.107-16.38-5.17-31.679-1.816-49.062c3.429-17.77,9.508-25.179,22.952-35.696c5.116,8.75,9.009,18.155,11.587,27.787 c2.885,10.797-2.108,17.756-1.887,29.043c9.016-27.753,24.1-28.312,48.911-31.897c1.217,13.409-3.38,34.501-10.594,45.221 c-8.25,12.259-19.378,20.187-32.263,25.779c-16.957,7.362-26.106,10.692-36.044,27.09c-14.141,23.341-29.859,47.279-51.421,64.229 c-20.019,15.737-55.577,27.382-79.919,27.594c-32.447,0.285-64.502,1.544-93.042-14.271c-24.048-13.325-41.616-31.512-57.1-54.671 c-4.078-6.102-5.675-13.266-9.747-19.346c-5.534-8.265-10.559-6.002-17.385-10.795c-11.927-8.378,4.19-28.58,13.417-34.331"
fill="#F7DFF3" />
<path
id="path12"
d="M185.766,139.689c-1.369,3.194-13.844,14.104-18.472,14.786c-9.973,1.467-16.968-10.333-27.009-10.561 c-2.057,5.782,8.974,12.097,12.816,16.617c6.371,7.496,6.95,8.932,16.698,8.094c8.976-0.772,15.682-3.333,24.327-0.799 c8.581,2.519,14.453,4.182,22.418,5.146c15.157,1.835,39.012,2.346,51.086,11.265c11.368,8.399,16.196,21.587,29.653,28.568 c14.019,7.271,27.363,18.312,42.49,22.745c0.955-1.461,1.454-3.359,1.649-4.813c-5.617-3.307-10.212-6.793-14.469-11.786 c-1.516-1.779-0.332-5.585-2.857-7.892c-1.601-1.463-7.582-1.327-10.059-2.781c-8.958-5.268-23.617-22.911-22.188-33.168 c1.393-9.997,16.089-26.739,22.009-35.097c9.849-13.907,12.447-21.519,10.457-38.644c-6.609-1.825-11.963,19.067-15.438,24.617 c-4.899,7.828-10.167,17.236-18.72,21.256c-15.611-16.87-36.387-2.918-52.052-8.563c-11.644-4.197-13.379-1.795-24.591,1.849 c-8.974,2.916-20.074,1.569-28.625,2.279"
fill="#DFEAEA" />
<path
id="path14"
d="M129.111,128.649c7.511,11.663,20.2,20.518,32.301,26.873c8.455-3.931,31.88-13.027,10.363-20.58 c-2.542-0.891-6.909,1.209-9.918,0.661c-3.988-0.727-7.133-3.393-10.443-4.33c-5.008-1.416-15.734-4.886-20.99-1.98 c0.499,1.56,1.145,2.434,1.832,3.606"
fill="#F2E9D7" />
<path
id="path16"
d="M152.468,94.796c-0.477-3.437-12.637-4.495-18.28-0.644c-3.699,2.524-11.741,16.624-12.201,20.086 c-1.038,7.784,5.543,15.402,12.629,17.072c3.486,0.822,7.845-1.964,10.357-1.355c4.948,1.198,3.957,4.78,8.025,6.263 c7.341,2.677,12.519-3.384,19.067,0.849c7.521,4.86,4.991,9.935,15.975,9.502c4.881-0.192,17.235-0.423,21.472-2.888 c10.632-6.187,1.85-10.946,2.056-19.062c0.222-8.76,7.221-10.841,1.237-19.853c-4.28-6.445-12.593-10.999-19.789-12.324 c-13.104-2.414-28.244,3.626-40.84,3.393"
fill="#E0E099" />
<path
id="path18"
d="M82.609,147.635c-6.449,0-6.449,10,0,10S89.057,147.635,82.609,147.635z"
fill="#F9F9F9" />
</svg>

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

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

@ -7886,6 +7886,12 @@ html {
.is-fullheight {
min-height: calc(100vh - ( 3.25rem )); }
.is-fullwidth-container {
width: 100%; }
.is-fullheight-container {
height: 100%; }
.is-fullwidth {
width: 100vw;
overflow: hidden; }
@ -7978,11 +7984,18 @@ video {
border: solid 1px rgba(56, 181, 187, 0.3);
flex-basis: 100%; }
.has-wrap {
flex-wrap: wrap; }
.book-thumb {
transition: all .2s;
z-index: inherit;
flex-basis: 12%;
text-align: center; }
text-align: center;
min-width: 180px;
background-color: whitesmoke;
padding: 5px;
margin: 5px; }
.book-thumb.enabled {
cursor: pointer; }
@ -8003,11 +8016,18 @@ video {
height: 100%; }
.fade-enter-active, .fade-leave-active {
transition: opacity .2s; }
transition: opacity 0.2s ease-in-out;
position: absolute; }
.fade-enter-active {
transition-delay: 0.2s; }
.fade-enter, .fade-leave-to {
opacity: 0; }
.fade-enter-to, .fade-leave {
opacity: 1; }
.slide-left-enter-active,
.slide-left-leave-active,
.slide-right-enter-active,
@ -8017,6 +8037,10 @@ video {
transition-timing-function: ease-in-out;
overflow: hidden; }
.slide-left-enter-active,
.slide-right-enter-active {
transition-delay: 0.2s; }
.slide-left-enter,
.slide-right-leave-active {
opacity: 0;
@ -8036,6 +8060,9 @@ video {
.is-justify-centered {
justify-content: center; }
.is-justify-between {
justify-content: space-between; }
.video-side-bar {
height: calc(100vh - ( 3.25rem )); }
.video-side-bar video {
@ -8085,3 +8112,73 @@ video {
display: flex;
flex-flow: column;
justify-content: space-between; }
.page-editor .croppa-container {
border: 2px solid whitesmoke;
border-radius: 8px;
color: white; }
.book-stitch-preview-left::before {
content: '';
height: 100%;
position: absolute;
width: 50%;
left: -50%;
top: 0;
mask-image: linear-gradient(to left, rgba(0, 0, 0, 0.3) 0%, transparent 100%);
background: url("/images/default-user-avatar.png"); }
.stitch-preview-right {
border: solid 1px whitesmoke; }
.stitch-preview-right::after {
content: '';
height: 100%;
position: absolute;
width: 100%;
left: 0;
top: 0;
background: linear-gradient(to right, transparent 0%, white 80%); }
.stitch-preview-left {
border: solid 1px whitesmoke; }
.stitch-preview-left::after {
content: '';
height: 100%;
position: absolute;
width: 100%;
left: 0;
top: 0;
background: linear-gradient(to left, transparent 0%, white 80%); }
.book-stitch-preview-right::after {
content: '';
background: url("/images/default-user-avatar.png"); }
.edit-page-controllers {
border-radius: 15px;
background-color: rgba(134, 134, 134, 0.1); }
.book-uploading-overlay {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.6);
z-index: 100; }
.height-max-view {
max-height: calc(99vh - ( 3.25rem )); }
.book-uploading-overlay-content {
position: relative;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
max-width: 600px; }
.book-uploading-overlay-content .title {
color: whitesmoke;
opacity: 1; }
.book-uploading-overlay-content .subtitle {
color: whitesmoke;
opacity: 0.8; }

View file

@ -221,6 +221,12 @@ $sizes: (
// overflow-y: auto;
// }
}
.is-fullwidth-container{
width:100%;
}
.is-fullheight-container{
height:100%;
}
.is-fullwidth{
width: 100vw;
@ -336,6 +342,9 @@ video{
flex-basis: 100%;
// max-width: 15vh;
}
.has-wrap{
flex-wrap: wrap;
}
.book-thumb{
// cursor: not-allowed;
@ -343,6 +352,10 @@ video{
z-index: inherit;
flex-basis: 12%;
text-align: center;
min-width: 180px;
background-color: whitesmoke;
padding: 5px;
margin: 5px;
}
.book-thumb.enabled{
cursor: pointer;
@ -368,11 +381,19 @@ video{
}
//Fade vue transition
.fade-enter-active, .fade-leave-active {
transition: opacity .2s;
transition: opacity 0.2s ease-in-out;
position: absolute;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
.fade-enter-active{
transition-delay: 0.2s;
}
.fade-enter, .fade-leave-to{
opacity: 0;
}
.fade-enter-to, .fade-leave{
opacity: 1;
}
.slide-left-enter-active,
.slide-left-leave-active,
@ -383,6 +404,10 @@ video{
transition-timing-function: ease-in-out;
overflow: hidden;
}
.slide-left-enter-active,
.slide-right-enter-active{
transition-delay: 0.2s;
}
.slide-left-enter,
.slide-right-leave-active {
@ -405,6 +430,10 @@ video{
.is-justify-centered{
justify-content: center;
}
.is-justify-between{
justify-content: space-between;
}
.video-side-bar{
height: calc(100vh - ( #{$navbar-height} ) );
@ -465,3 +494,100 @@ video{
flex-flow: column;
justify-content: space-between;
}
//edit page
.page-editor{
.croppa-container {
border: 2px solid whitesmoke;
border-radius: 8px;
color: white;
}
}
.book-thumb.page-preview{
// flex-basis: unset;
}
.book-stitch-preview-left{
&::before{
content: '';
height: 100%;
position: absolute;
// background-color: black;
width:50%;
left:-50%;
top:0;
mask-image: linear-gradient(to left, rgba(0,0,0,.3) 0%, transparent 100%);
background: url('/images/default-user-avatar.png');
}
}
.stitch-preview-right{
// position: absolute;
// top:0;
// right:0;
border: solid 1px whitesmoke;
&::after{
content: '';
height: 100%;
position: absolute;
// background-color: black;
width:100%;
left:0;
top:0;
background: linear-gradient(to right, transparent 0%, white 80%);
}
}
.stitch-preview-left{
// position: absolute;
// top:0;
// left:0;
border: solid 1px whitesmoke;
&::after{
content: '';
height: 100%;
position: absolute;
// background-color: black;
width:100%;
left:0;
top:0;
background: linear-gradient(to left, transparent 0%, white 80%);
}
}
.book-stitch-preview-right{
&::after{
content: '';
background: url('/images/default-user-avatar.png');
}
}
.edit-page-controllers{
border-radius: 15px;
background-color: rgba(134, 134, 134, 0.1);
}
.book-uploading-overlay{
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: rgba($color: #000000, $alpha: .6);
z-index: 100;
}
.height-max-view{
max-height: calc(99vh - ( #{$navbar-height} ) );
}
.book-uploading-overlay-content{
position: relative;
top:50%;
left:50%;
transform: translate(-50%, -50%);
max-width: 600px;
.title{
color: whitesmoke;
opacity: 1;
}
.subtitle{
color: whitesmoke;
opacity: 0.8;
}
}

View file

@ -8,6 +8,7 @@
:prevent-white-space="true"
:show-remove-button="false"
:disable-drag-to-move="isDefaultImage"
:accept="'image/*'"
@new-image="isDefaultImage=false;zoomState=1"
></croppa>
</figure>

View file

@ -14,6 +14,8 @@ import Home from "../views/home.vue";
import Settings from "../views/settings.vue";
import Call from "../views/call.vue";
import ChildProfile from "../views/child_profile.vue";
import EditBook from "../views/edit_book.vue";
import BookOfflineViewer from "../views/BookOfflineViewer.vue";
// Call Views
import CallLobby from "../views/call_views/Lobby.vue";
@ -30,6 +32,14 @@ const routes: RouteConfig[] = [
path: "/settings",
component: Settings
},
{
path: "/create/book",
component: EditBook
},
{
path: "/book/:id",
component: BookOfflineViewer
},
{
path: "/call/:id",
component: Call,

View file

@ -0,0 +1,137 @@
<template>
<div class="is-fullwidth is-fullheight-container p-l-lg p-r-lg">
<Loading v-if="loading" />
<div :class="`is-fullheight-container ${flipbookRef ? '' : 'is-transparent'}`" v-else>
<div class="book-view m-sm m-r-md">
<div
class="go-left m-r-sm"
style="display: inline-block; align-items: center; position: absolute; left:0px; top:0px"
>
<button
class="button book-flip-buttons"
:disabled="!canFlipLeft"
@click="onLeftClicked()"
>
<i class="fa fa-fw fa-arrow-left"></i>
</button>
</div>
<flipbook
class="flipbook"
:pages="pages"
:forwardDirection="book.ltr ? 'right': 'left'"
:zooms="null"
:enabled="true"
@on-mounted="bookMounted()"
ref="flipbook"
v-slot="flipbook"
>
<!-- @flip-left-start="onFlip('left')" -->
<!-- @flip-right-start="onFlip('right')" -->
<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: inline-block; align-items: center; position: absolute; right:0px; top:0px"
>
<button
class="button book-flip-buttons"
:disabled="!canFlipRight"
@click="onRightClicked()"
>
<i class="fa fa-fw fa-arrow-right"></i>
</button>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { mapGetters, mapActions } from "vuex";
import Flipbook from "../components/flipbook/flipbook.cjs.js";
import Loading from "../../shared/components/Loading/Loading.vue";
export default {
name: "BookOfflineViewer",
components: {
Flipbook,
Loading
},
created() {
const bookId = Number(this.$route.params.id);
if (!this.user || !bookId) {
this.$router.replace({ path: `/` });
}
this.user.books.forEach(b => {
if (this.book) return;
if (b.id === bookId) {
console.log("Found Book");
this.book = b;
}
});
if (!this.book) {
this.notify({ message: "Book Not Found!", level: "danger" });
this.$router.replace({ path: `/` });
} else {
// create pages
// /u/books/:bookId/page/:pageNumber
const pages = [null];
for (let i = 1; i < this.book.pages + 1; i++) {
pages.push(`/u/books/${bookId}/page/${i}`);
}
this.pages = pages;
this.loading = false;
}
},
methods: {
bookMounted() {
console.log("Book Mounted!");
if (this.$refs.flipbook) {
console.log("Found!");
this.flipbookRef = true;
// this.$refs.flipbook.onResize();
// console.log("resized");
} else {
console.log("Still Null!!");
}
},
onLeftClicked() {
this.$refs.flipbook.flipLeft();
},
onRightClicked() {
this.$refs.flipbook.flipRight();
},
...mapActions(["notify"])
},
computed: {
canFlipLeft() {
return this.flipbookRef && this.$refs.flipbook.canFlipLeft;
},
canFlipRight() {
return this.flipbookRef && this.$refs.flipbook.canFlipRight;
},
...mapGetters(["user"])
},
data() {
return {
loading: true,
book: <IBook>null,
pages: <string[]>[],
flipbookRef: null
};
}
};
interface IBook {
id: number;
pages: number;
user_id?: number;
title: string;
ltr: boolean;
created_at: string;
}
</script>

View file

@ -1,7 +1,7 @@
<template>
<div>
<Loading v-if="loading" />
<div :class="`book-view ${flipbookRef ? '' : 'is-transparent'}`" v-else>
<div :class="`is-fullheight-container ${flipbookRef ? '' : 'is-transparent'}`" v-else>
<div class="book-view m-sm m-r-md">
<div
class="go-left m-r-sm"

View file

@ -4,7 +4,7 @@
<h2 class="subtitle">
<i class="fa fa-fw fa-book"></i> Select A Book
</h2>
<div class="is-flex m-b-md">
<div class="is-flex m-b-md has-wrap">
<div
:class="['book-thumb', 'm-l-md', {'enabled': callManager.isHost}]"
v-for="(book, index) in callManager.books"

View file

@ -0,0 +1,725 @@
<template>
<div class="is-fullwidth is-fullheight-container p-l-lg p-r-lg">
<transition name="fade">
<div class="book-uploading-overlay" v-if="uploading">
<div class="section book-uploading-overlay-content has-text-centered">
<h1 class="title">Uploading...</h1>
<h1 class="subtitle">Be pacient, This can take a while</h1>
<progress class="progress is-small is-primary" max="100">15%</progress>
</div>
</div>
</transition>
<div class="columns is-flex is-fullheight-container">
<div class="column is-3">
<!-- Properties -->
<div class="card m-b-md">
<div class="card-content">
<h1 class="subtitle">
<i class="fa fa-book"></i> Properties
</h1>
<div class="book-properties">
<div class="field">
<label class="label">Book Title:</label>
<input class="input" type="text" placeholder="My Book" v-model="book.title" />
</div>
<div class="field">
<label class="label">Book Author:</label>
<input class="input" type="text" placeholder="Savta Cochi" v-model="book.author" />
</div>
<div class="field">
<label class="label">Language Direction:</label>
<label>
<input
class="checkbox"
type="checkbox"
v-model="book.ltr"
aria-label="Book direction"
/>
{{book.ltr ? "Left To Right" : "Right To Left"}}
<i
:class="`fa fa-fw fa-arrow-${book.ltr?'right':'left'}`"
></i>
</label>
</div>
<div class="field">
<label class="label">Total page count: #{{pages.length}}</label>
</div>
</div>
</div>
<footer class="card-footer">
<a class="card-footer-item is-success" @click="onUploadClicked()">
<i class="fa fa-fw fa-upload"></i> Upload Book
</a>
</footer>
</div>
<!-- Pages -->
<div class="card">
<aside class="menu card-content">
<p class="menu-label">Pages</p>
<ul class="menu-list">
<li v-for="page in pages" :key="page.id">
<a
:class="{'is-active' : currentPage === page.id}"
@click="onPageClicked(page.id)"
>{{page.text}}</a>
</li>
</ul>
</aside>
<footer class="card-footer">
<a class="card-footer-item" @click="onAddPageClicked()">
<i class="fa fa-fw fa-plus"></i> Add Page
</a>
</footer>
</div>
</div>
<div class="column is-9">
<div class="card is-fullheight-container bg-flower height-max-view">
<div class="card-content is-fullheight-container">
<div class="tabs-container has-text-centered m-b-lg">
<div class="tabs is-centered">
<ul>
<li :class="editMode ? 'is-active' : ''" @click="editMode=true">
<a>
<span class="icon is-small">
<i class="fa fa-pencil" aria-hidden="true"></i>
</span>
<span>Edit</span>
</a>
</li>
<li :class="!editMode ? 'is-active' : ''" @click="onEditClicked()">
<a>
<span class="icon is-small">
<i class="fa fa-eye" aria-hidden="true"></i>
</span>
<span>Preview</span>
</a>
</li>
</ul>
</div>
</div>
<div class="content is-fullheight-container">
<!-- Book Edit -->
<transition name="fade">
<div class="edit" v-if="editMode">
<div class="has-text-centered" v-if="currentPage < -1">
<h1 class="subtitle m-t-xl m-b-xl">This is exciting...</h1>
<div class="has-text-left">
<p>Here will be some more instructions in the future...</p>
<p>Also maybe some gifs fo help...</p>
<ul>
<li>Add a title and author for the book</li>
<li>Add first image (Cover of the book)</li>
<li>Try to fit the image as close as possible to edges of the "page"</li>
<li>You can modify the width/height while editing the cover</li>
<li>You can always zoom in/out and drag the image to make it fit better</li>
<li>Please click on preview to actualy see how the book will look like before uploading</li>
<li>Once all done. Click on "Upload Book"</li>
</ul>
</div>
<a class="button is-large is-rounded" @click="onAddPageClicked()">
<i class="fa fa-fw fa-plus"></i> Add Cover
</a>
</div>
<div class="page-editor" v-else>
<div class="columns">
<!-- Croppa -->
<div class="column has-text-centered">
<div v-if="pages[currentPage].loaded">
<h1 class="subtitle is-3">{{pages[currentPage].text}}</h1>
<div class="is-relative is-flex is-justify-centered">
<div
class="stitch-preview-left is-relative"
v-if="currentPage != 0 && currentPage % 2 === 0 && book.ltr"
:style="`width:${croppaWidth}px;height:${croppaHeight}px;`"
>
<img :src="pages[currentPage-1].image" alt />
</div>
<croppa
v-model="pages[currentPage].croppa"
:prevent-white-space="false"
:show-remove-button="false"
:accept="'image/*'"
:initial-image="pages[currentPage].image"
:width="croppaWidth"
:height="croppaHeight"
:disable-drag-to-move="false"
:disable-scroll-to-zoom="true"
:zoom-speed="1"
canvas-color="white"
@loading-end="onCroppaImageLoaded()"
></croppa>
<div
class="stitch-preview-right is-relative"
v-if="currentPage != 0 && currentPage % 2 === 0 && !book.ltr"
:style="`width:${croppaWidth}px;height:${croppaHeight}px;`"
>
<img :src="pages[currentPage-1].image" alt />
</div>
</div>
</div>
</div>
<!-- Controllers -->
<div
class="edit-page-controllers column is-3 is-flex-column is-justify-centered has-text-centered"
>
<div class="field" v-if="currentPage===0 && pages.length===1">
<label class="label">Page width</label>
<input
type="range"
:min="DEFAULT_PAGE_WIDTH-100"
:max="DEFAULT_PAGE_WIDTH"
v-model="bookWidth"
:disabled="!pages[currentPage].imageLoaded"
/>
<div class="is-flex is-justify-between">
<button
type="button"
class="button is-rounded is-outlined is-small"
:disabled="!pages[currentPage].imageLoaded || bookWidth<= DEFAULT_PAGE_WIDTH - 100"
@click="bookWidth-=2"
>
<i class="fa fa-fw fa-minus"></i>
</button>
<button
type="button"
class="button is-rounded is-outlined is-small"
:disabled="!pages[currentPage].imageLoaded || bookWidth >= DEFAULT_PAGE_WIDTH"
@click="bookWidth+=2"
>
<i class="fa fa-fw fa-plus"></i>
</button>
</div>
</div>
<div class="prev-page-preview" v-else>
<div
class="is-flex is-justify-centered"
v-if="pages[currentPage - 1] && pages[currentPage - 1].base64"
>
<div class="book-thumb page-preview">
<div class="book-text">
<div>Previouse Page</div>
</div>
<div class="book-cover is-flex">
<img
:src="pages[currentPage - 1] ? pages[currentPage - 1].base64: ''"
/>
</div>
<div class="book-text">
<div>{{pages[currentPage - 1].text}}</div>
</div>
</div>
</div>
</div>
<hr class="is-fullwidth-container" />
<div class="field" v-if="currentPage===0 && pages.length===1">
<label class="label">Page height</label>
<input
type="range"
:min="DEFAULT_PAGE_HEIGHT-50"
:max="DEFAULT_PAGE_HEIGHT"
v-model="bookHeight"
:disabled="!pages[currentPage].imageLoaded"
/>
<div class="is-flex is-justify-between">
<button
type="button"
class="button is-rounded is-outlined is-small"
:disabled="!pages[currentPage].imageLoaded || bookWidth<= DEFAULT_PAGE_HEIGHT - 50"
@click="bookHeight-=2"
>
<i class="fa fa-fw fa-minus"></i>
</button>
<button
type="button"
class="button is-rounded is-outlined is-small"
:disabled="!pages[currentPage].imageLoaded || bookWidth >= DEFAULT_PAGE_HEIGHT"
@click="bookHeight+=2"
>
<i class="fa fa-fw fa-plus"></i>
</button>
</div>
</div>
<div class="field"></div>
<div class="field">
<label class="label">Zoom Image</label>
<input
type="range"
min="0"
:max="DEFAULT_ZOOM*2"
v-model="pageZoom"
:disabled="!pages[currentPage].imageLoaded"
ref="zoomRangeSlider"
@mouseup="blurZoomSlider()"
/>
<div class="is-flex is-justify-between">
<button
type="button"
class="button is-rounded is-outlined is-small"
:disabled="!pages[currentPage].imageLoaded || pageZoom <= 0"
@click="zoom(false)"
>
<i class="fa fa-fw fa-minus"></i>
</button>
<button
type="button"
class="button is-rounded is-outlined is-small"
:disabled="!pages[currentPage].imageLoaded || pageZoom >= DEFAULT_ZOOM * 2"
@click="zoom(true)"
>
<i class="fa fa-fw fa-plus"></i>
</button>
</div>
</div>
<div class="rotations is-flex is-justify-between m-b-lg">
<label class="label">Rotate Image</label>
<button
class="button"
@click="onRotateClicked(false)"
:disabled="!pages[currentPage].imageLoaded"
>
<i class="fa fa-fw fa-rotate-left"></i>
</button>
<button
class="button"
@click="onRotateClicked(true)"
:disabled="!pages[currentPage].imageLoaded"
>
<i class="fa fa-fw fa-rotate-right"></i>
</button>
</div>
<div class="change-image">
<button
class="button is-fullwidth-container"
@click="pages[currentPage].croppa.chooseFile()"
>
<i class="fa fa-fw fa-refresh"></i> Change Image
</button>
</div>
<div class="remove-image" v-if="currentPage === pages.length-1">
<button
class="button is-danger is-fullwidth-container"
@click="deleteLastPage()"
>
<i class="fa fa-fw fa-trash"></i> Delete Page
</button>
</div>
<!-- TODO: Fix this shit - maybe fork the project? -->
<!-- <div class="field">
<label class="label">Rotate Image</label>
<input
type="range"
min="-180"
max="180"
v-model="pageRotation"
:disabled="!pages[currentPage].croppa.hasImage"
/>
</div>-->
</div>
</div>
</div>
</div>
</transition>
<!-- Book Preview -->
<transition name="fade">
<div
:class="`is-fullheight-container ${flipbookRef ? '' : 'is-transparent'}`"
v-if="!editMode"
>
<div class="book-view m-sm m-r-md">
<div
class="go-left m-r-sm"
style="display: inline-block; align-items: center; position: absolute; left:0px; top:0px"
>
<button
class="button book-flip-buttons"
:disabled="!canFlipLeft"
@click="onLeftClicked()"
>
<i class="fa fa-fw fa-arrow-left"></i>
</button>
</div>
<flipbook
class="flipbook"
:pages="previewPages"
:forwardDirection="book.ltr ? 'right': 'left'"
:zooms="null"
:enabled="true"
ref="flipbook"
v-slot="flipbook"
@on-mounted="bookMounted()"
>
<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: inline-block; align-items: center; position: absolute; right:0px; top:0px"
>
<button
class="button book-flip-buttons"
:disabled="!canFlipRight"
@click="onRightClicked()"
>
<i class="fa fa-fw fa-arrow-right"></i>
</button>
</div>
</div>
</div>
</transition>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { mapGetters, mapActions } from "vuex";
import Flipbook from "../components/flipbook/flipbook.cjs.js";
import Croppa from "vue-croppa";
import Services from "../../services";
const DEFAULT_PAGE_WIDTH = 350;
const DEFAULT_PAGE_HEIGHT = 350;
const DEFAULT_ZOOM = 250;
const MIME_TYPE = "image/jpeg";
const COMPRESSION_RATE = 0.4;
export default {
name: "EditBook",
props: ["editBook"],
components: {
Flipbook,
Croppa: Croppa.component
},
watch: {
editMode: function(newVal) {
if (!newVal) {
this.currentPage = -7;
//Update previewPages
this.previewPages = [null, ...this.pages.map(page => page.image)];
} else {
if (this.currentPage < 0 && this.pages.length) this.currentPage = 0;
this.flipbookRef = false;
}
},
currentPage: async function(currentPage, lastPage) {
/// Save progress on latest page
console.log(lastPage, currentPage);
if (this.pages[lastPage] && lastPage >= 0) {
const imageBlob = await this.pages[lastPage].croppa.promisedBlob(
MIME_TYPE,
COMPRESSION_RATE
);
if (imageBlob) {
let url = URL.createObjectURL(imageBlob);
this.pages[lastPage].base64 = this.pages[
lastPage
].croppa.generateDataUrl(MIME_TYPE, COMPRESSION_RATE);
this.pages[lastPage].image = url;
}
}
if (currentPage >= 0) {
// Load new page croppa
this.pages[currentPage].loaded = false;
this.$nextTick(function() {
console.log("tick");
this.pages[currentPage].loaded = true;
});
}
this.pageRotation = 0;
this.pageZoom = DEFAULT_ZOOM;
return true;
},
pageRotation: function(newAngle) {
console.log(newAngle);
const page = this.pages[this.currentPage];
const canvas: HTMLCanvasElement = page.croppa.getCanvas();
const ctx: CanvasRenderingContext2D = page.croppa.getContext();
ctx.rotate((newAngle * Math.PI) / 180);
ctx.drawImage(canvas, 0, 0);
ctx.setTransform(1, 0, 0, 1, 0, 0);
this.$nextTick(function() {
// page.croppa.moveDownwards(1);
});
// page.croppa.rotate(newAngle / 90);
},
pageZoom: function(newVal, oldVal) {
const page = this.pages[this.currentPage];
console.log(newVal, oldVal);
const delta = Math.abs(newVal - oldVal);
if (!page.croppa.zoomOut) return;
if (newVal < oldVal) {
//zoomOut
for (let i = delta; i > 0; i--) page.croppa.zoomOut();
} else {
//zoomIn
for (let i = delta; i > 0; i--) page.croppa.zoomIn();
}
}
},
created() {
if (this.editBook) this.book = this.editBook;
else
this.book = {
name: "",
ltr: true,
pages: 0,
user_id: this.user.id
};
},
methods: {
...mapActions(["notify", "getUser"]),
///
deleteLastPage() {
this.pages.pop();
if (!this.pages.length) this.currentPage = -7;
else this.currentPage = this.pages.length - 1;
},
onRotateClicked(clockwise: boolean) {
const page = this.pages[this.currentPage];
page.croppa.rotate(clockwise ? 1 : -1);
},
onPageClicked(pageIndex) {
this.currentPage = pageIndex;
this.editMode = true;
},
onCroppaImageLoaded() {
this.pages[this.currentPage].imageLoaded = true;
},
async promiseAllProgress(
promises: Promise<any>[],
callback: (done: number, total: number) => void
): Promise<any> {
let counter = 0;
callback(counter, promises.length);
for (let i = 0; i < promises.length; i++) {
const promise = promises[i];
promise.then(async val => {
counter++;
callback(counter, promises.length);
return val;
});
}
return Promise.all(promises);
},
async onUploadClicked() {
//TODO: Better validations
if (!this.book.title || !this.book.title.length) {
this.notify({
message: "Book needs a title!",
level: "warning"
});
return;
}
if (!this.book.author || !this.book.author.length) {
this.notify({
message: "Book sure has an author!",
level: "warning"
});
return;
}
if (this.pages.length < 4) {
this.notify({
message: "A book need to have at least 4 pages",
level: "warning"
});
return;
}
if (!this.pages[this.pages.length - 1].base64) {
this.notify({
message: "You last page is empty. Delete or update",
level: "warning"
});
return;
}
this.uploading = true;
try {
const resp = await Services.ApiService.uploadBook({
title: this.book.title,
author: this.book.author,
ltr: this.book.ltr,
pages: this.pages.map(
p =>
p.base64 || p.croppa.generateDataUrl(MIME_TYPE, COMPRESSION_RATE)
)
});
if (resp.code === 0) {
this.notify({
message: `Woop Woop!! ${this.book.title} has been added!`,
level: "success"
});
this.getUser();
this.$router.replace({ path: `/` });
} else {
this.notify({
message: `Something went wrong!`,
level: "danger"
});
}
} catch (e) {
console.log(`Error... ${e.message}`);
}
this.uploading = false;
},
async onEditClicked() {
const lastPage = this.currentPage;
if (lastPage >= 0) {
const imageBlob = await this.pages[lastPage].croppa.promisedBlob(
MIME_TYPE,
COMPRESSION_RATE
);
if (!imageBlob) {
this.notify({
message: "Cant have an empty page.",
level: "warning"
});
this.currentPage = lastPage;
return;
}
let url = URL.createObjectURL(imageBlob);
this.pages[lastPage].base64 = this.pages[
lastPage
].croppa.generateDataUrl(MIME_TYPE, COMPRESSION_RATE);
this.pages[lastPage].image = url;
this.currentPage = -7;
this.editMode = false;
} else {
this.notify({
message: `Please add pages before preview`,
level: "warning"
});
}
},
async onAddPageClicked() {
const lastPage = this.pages.length - 1;
if (lastPage < 0) {
this.pages.push({
text: "Cover",
id: 0,
loaded: false,
croppa: {},
image: null,
imageLoaded: false,
base64: null
});
this.currentPage = 0;
//return;
} else {
console.log("In else");
const nextPage = lastPage + 1;
const imageBlob = await this.pages[lastPage].croppa.promisedBlob(
MIME_TYPE,
COMPRESSION_RATE
);
if (!imageBlob) {
this.notify({
message: "Your last page is still empty",
level: "warning"
});
this.currentPage = lastPage;
return;
}
this.editMode = true;
let url = URL.createObjectURL(imageBlob);
this.pages[lastPage].base64 = this.pages[
lastPage
].croppa.generateDataUrl(MIME_TYPE, COMPRESSION_RATE);
this.pages[lastPage].image = url;
this.pages.push({
text: `Page ${nextPage}`,
id: nextPage,
loaded: false,
croppa: {},
image: null,
imageLoaded: false,
base64: null
});
this.currentPage = nextPage;
}
},
bookMounted() {
if (this.$refs.flipbook) {
console.log("Found!");
this.flipbookRef = true;
// this.$refs.flipbook.onResize();
// console.log("resized");
} else {
console.log("Still Null!!");
}
},
onLeftClicked() {
console.time("Flip Left");
this.$refs.flipbook.flipLeft();
console.timeEnd("Flip Left");
return true;
},
onRightClicked() {
this.$refs.flipbook.flipRight();
return true;
},
zoom(zoomIn: boolean) {
const amount = zoomIn ? 2 : -2;
this.pageZoom = Number(this.pageZoom) + amount;
},
blurZoomSlider() {
const slider: HTMLInputElement = this.$refs.zoomRangeSlider;
if (slider) {
slider.blur();
}
}
},
computed: {
...mapGetters(["user"]),
//
bookPages() {
return this.pages.slice(1);
},
croppaClass() {
if (this.currentPage != 0 && this.currentPage % 2 == 0) {
return `book-stitch-preview-${this.book.ltr ? "left" : "right"}`;
}
return true;
},
canFlipLeft() {
return this.flipbookRef && this.$refs.flipbook.canFlipLeft;
},
canFlipRight() {
return this.flipbookRef && this.$refs.flipbook.canFlipRight;
},
croppaWidth() {
return Number(this.bookWidth);
},
croppaHeight() {
return Number(this.bookHeight);
}
},
data() {
return {
book: {
title: "",
author: "",
rtl: true
},
pages: [],
bookWidth: DEFAULT_PAGE_WIDTH,
bookHeight: DEFAULT_PAGE_HEIGHT,
previewPages: [],
editMode: true,
currentPage: -7,
flipbookRef: false,
pageRotation: 0,
pageZoom: DEFAULT_ZOOM,
uploading: false,
errors: {},
DEFAULT_PAGE_WIDTH,
DEFAULT_PAGE_HEIGHT,
DEFAULT_ZOOM
};
}
};
</script>

View file

@ -138,8 +138,13 @@
<h2 class="subtitle">
<i class="fa fa-fw fa-book"></i> My Books
</h2>
<div class="is-flex m-b-md is-justify-centered">
<div class="book-thumb m-l-md" v-for="book in user.books" :key="book.id">
<div class="is-flex m-b-md is-justify-centered has-wrap">
<div
class="book-thumb enabled m-l-md"
v-for="book in user.books"
:key="book.id"
@click="goToBook(book)"
>
<div class="book-cover">
<figure class="image is-2by3 m-a">
<img :src="`/u/books/${book.id}/thumbnail`" />
@ -230,6 +235,9 @@ export default {
case "child":
this.showAddChildModal = true;
break;
case "book":
this.$router.push({ path: `/create/${action}` });
break;
default:
this.notify({
message: `Add ${action} button clicked. Still not working`
@ -243,6 +251,9 @@ export default {
goChildProfile(connection) {
this.$router.push({ path: `/child/${connection.id}` });
},
goToBook(book) {
this.$router.push({ path: `/book/${book.id}` });
},
async onChildCreated(child) {
this.loading = true;
await this.getUser();

View file

@ -19,6 +19,24 @@ export default class ApiService {
}
}
static async uploadBook(payload: { title: string, author: string, pages: string[], ltr: boolean }) {
// console.log(payload);
// return { code: 0 }
const options = {
method: 'POST',
body: JSON.stringify(payload),
headers: {
'Content-Type': 'application/json'
}
}
try {
return (await fetch('/api/v1/client/book/create', options)).json();
} catch (e) {
console.error(`uploadBook ERROR: ${e.message}`);
throw e;
}
}
static async updateUser(payload: { name?: string; avatar?: string; profile_cover?: string; email?: string }) {
const options = {
method: 'PUT',

View file

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

View file

@ -49,6 +49,7 @@ Route
Route.get('child/:id', 'ClientApiController.getChild');
Route.post('child/:id', 'ClientApiController.updateChild');
Route.post('call/create', 'ClientApiController.createCall');
Route.post('book/create', 'ClientApiController.createBook');
})
.prefix('api/v1/client')
.middleware(['auth']);
@ -60,7 +61,9 @@ Route
.get(
'/u/call/:callId/books/:bookId/page/:pageNumber',
'BookApiController.getPage')
.middleware(['auth', 'BookContext', 'CallContext', 'BookPageAuth']);
.middleware(['auth', 'BookContext', 'CallContext', 'BookCallPageAuth']);
Route.get('/u/books/:bookId/page/:pageNumber', 'BookApiController.getPage')
.middleware(['auth', 'BookContext', 'BookPageAuth']);
/**
* Public book thumbnail
*/