This repository has been archived on 2024-12-15. You can view files and clone it, but cannot push or open issues or pull requests.
seepur/resources/scripts/applications/home/views/edit_book.vue

726 lines
27 KiB
Vue
Raw Permalink Normal View History

<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">
2020-05-25 15:18:37 +00:00
<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>