Made an image picker component + more polished create new child

This commit is contained in:
Sagi Dayan 2020-05-20 21:18:56 -04:00
parent 09ff1e38a6
commit 05876f4492
16 changed files with 7523 additions and 363 deletions

View file

@ -39,7 +39,6 @@ class ClientApiController {
const body = request.body;
if (body.avatar) {
const file = await FileUtils.saveBase64File(body.avatar);
console.log(file);
body.avatar = `/u/images/${file.fileName}`;
} else {
body.avatar = `/images/default-child-avatar.png`;
@ -160,6 +159,32 @@ class ClientApiController {
//
}
async updateChild({request, auth, response}) {
const childId = request.params.id;
const userId = auth.user.id;
const {name, dob, profile_cover, avatar} = request.body;
const isParent = await UserChildUtils.isParentOf(userId, childId);
if (!isParent) {
response.status(403);
response.send(
{code: 403, message: `You have no permission to edit this child`});
return false;
}
// TODO: Add validation;
const child = await Child.find(childId);
child.dob = dob || child.dob;
if (profile_cover) {
const file = await FileUtils.saveBase64File(profile_cover);
child.profile_cover = `/u/images/${file.fileName}`;
}
if (avatar) {
const file = await FileUtils.saveBase64File(body.avatar);
child.avatar = `/u/images/${file.fileName}`;
}
await child.save();
return {code: 0, data: {child}};
}
async setChildProfileCover({request, auth, response}) {
try {
const rules = {

14
package-lock.json generated
View file

@ -2758,6 +2758,11 @@
"integrity": "sha512-UiRZmBYd1HdVVdFKy7PuLVx9e2NS7SMyx7QpWvFjiklYrLJKpLd19cRnRNqlw4zYa7vVejS3c8JUVobX241zHQ==",
"dev": true
},
"canvas-exif-orientation": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/canvas-exif-orientation/-/canvas-exif-orientation-0.4.0.tgz",
"integrity": "sha1-tIfzcBmYqeh56xBAELKlgRU2i2s="
},
"capture-stack-trace": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz",
@ -9564,6 +9569,15 @@
"resolved": "https://registry.npmjs.org/vue/-/vue-2.6.11.tgz",
"integrity": "sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ=="
},
"vue-croppa": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/vue-croppa/-/vue-croppa-1.3.8.tgz",
"integrity": "sha512-WwYgEKscTCD7BzhnbfRJfzWIU6RcMq2JRimB3aI5gGzpADmpKuqmDh9+oVfiZaEnpmRthgXZxcAvbxU6CeIU9w==",
"requires": {
"canvas-exif-orientation": "^0.4.0",
"object-assign": "^4.1.1"
}
},
"vue-hot-reload-api": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz",

View file

@ -50,6 +50,7 @@
"typescript": "^3.7.5",
"uuid": "^8.0.0",
"vue": "^2.6.11",
"vue-croppa": "^1.3.8",
"vue-router": "^3.1.5",
"vuex": "^3.1.2"
},

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

@ -1,5 +1,6 @@
/*! bulma.io v0.8.2 | MIT License | github.com/jgthms/bulma */
@import url("https://fonts.googleapis.com/css?family=Montserrat&display=swap");
@import url("/vendor/croppa/vue-croppa.min.css");
@keyframes spinAround {
from {
transform: rotate(0deg); }
@ -8079,3 +8080,8 @@ video {
color: whitesmoke;
font-size: 50px;
padding-top: 2px; }
.is-flex-column {
display: flex;
flex-flow: column;
justify-content: space-between; }

View file

@ -0,0 +1 @@
.sk-fading-circle{position:absolute}.sk-fading-circle .sk-circle{width:100%;height:100%;position:absolute;left:0;top:0}.sk-fading-circle .sk-circle .sk-circle-indicator{display:block;margin:0 auto;width:15%;height:15%;border-radius:100%;-webkit-animation:sk-circleFadeDelay 1s infinite ease-in-out both;animation:sk-circleFadeDelay 1s infinite ease-in-out both}.sk-fading-circle .sk-circle2{-webkit-transform:rotate(30deg);transform:rotate(30deg)}.sk-fading-circle .sk-circle3{-webkit-transform:rotate(60deg);transform:rotate(60deg)}.sk-fading-circle .sk-circle4{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.sk-fading-circle .sk-circle5{-webkit-transform:rotate(120deg);transform:rotate(120deg)}.sk-fading-circle .sk-circle6{-webkit-transform:rotate(150deg);transform:rotate(150deg)}.sk-fading-circle .sk-circle7{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.sk-fading-circle .sk-circle8{-webkit-transform:rotate(210deg);transform:rotate(210deg)}.sk-fading-circle .sk-circle9{-webkit-transform:rotate(240deg);transform:rotate(240deg)}.sk-fading-circle .sk-circle10{-webkit-transform:rotate(270deg);transform:rotate(270deg)}.sk-fading-circle .sk-circle11{-webkit-transform:rotate(300deg);transform:rotate(300deg)}.sk-fading-circle .sk-circle12{-webkit-transform:rotate(330deg);transform:rotate(330deg)}.sk-fading-circle .sk-circle2 .sk-circle-indicator{-webkit-animation-delay:-.91667s;animation-delay:-.91667s}.sk-fading-circle .sk-circle3 .sk-circle-indicator{-webkit-animation-delay:-.83333s;animation-delay:-.83333s}.sk-fading-circle .sk-circle4 .sk-circle-indicator{-webkit-animation-delay:-.75s;animation-delay:-.75s}.sk-fading-circle .sk-circle5 .sk-circle-indicator{-webkit-animation-delay:-.66667s;animation-delay:-.66667s}.sk-fading-circle .sk-circle6 .sk-circle-indicator{-webkit-animation-delay:-.58333s;animation-delay:-.58333s}.sk-fading-circle .sk-circle7 .sk-circle-indicator{-webkit-animation-delay:-.5s;animation-delay:-.5s}.sk-fading-circle .sk-circle8 .sk-circle-indicator{-webkit-animation-delay:-.41667s;animation-delay:-.41667s}.sk-fading-circle .sk-circle9 .sk-circle-indicator{-webkit-animation-delay:-.33333s;animation-delay:-.33333s}.sk-fading-circle .sk-circle10 .sk-circle-indicator{-webkit-animation-delay:-.25s;animation-delay:-.25s}.sk-fading-circle .sk-circle11 .sk-circle-indicator{-webkit-animation-delay:-.16667s;animation-delay:-.16667s}.sk-fading-circle .sk-circle12 .sk-circle-indicator{-webkit-animation-delay:-83.33ms;animation-delay:-83.33ms}@-webkit-keyframes sk-circleFadeDelay{0%,100%,39%{opacity:0}40%{opacity:1}}@keyframes sk-circleFadeDelay{0%,100%,39%{opacity:0}40%{opacity:1}}.croppa-container{display:inline-block;cursor:pointer;-webkit-transition:all .3s;transition:all .3s;position:relative;font-size:0;-ms-flex-item-align:start;align-self:flex-start;background-color:#e6e6e6}.croppa-container canvas{-webkit-transition:all .3s;transition:all .3s}.croppa-container:hover{opacity:.7}.croppa-container.croppa--dropzone{-webkit-box-shadow:inset 0 0 10px #333;box-shadow:inset 0 0 10px #333}.croppa-container.croppa--dropzone canvas{opacity:.5}.croppa-container.croppa--disabled-cc{cursor:default}.croppa-container.croppa--disabled-cc:hover{opacity:1}.croppa-container.croppa--has-target{cursor:move}.croppa-container.croppa--has-target:hover{opacity:1}.croppa-container.croppa--has-target.croppa--disabled-mz{cursor:default}.croppa-container.croppa--disabled{cursor:not-allowed}.croppa-container.croppa--disabled:hover{opacity:1}.croppa-container.croppa--passive{cursor:default}.croppa-container.croppa--passive:hover{opacity:1}.croppa-container svg.icon-remove{position:absolute;background:#fff;border-radius:50%;-webkit-filter:drop-shadow(-2px 2px 2px rgba(0,0,0,.7));filter:drop-shadow(-2px 2px 2px rgba(0,0,0,.7));z-index:10;cursor:pointer;border:2px solid #fff}

View file

@ -5,6 +5,8 @@
@import './mixins.scss';
@import "../../node_modules/bulma/bulma.sass";
@import "./overrides.scss";
@import url('/vendor/croppa/vue-croppa.min.css');
// @import '../../node_modules/animate.css/source/_base.css';
// nav.navbar {
// padding-left: 5rem;
@ -457,3 +459,9 @@ video{
padding-top: 2px;
}
}
.is-flex-column{
display: flex;
flex-flow: column;
justify-content: space-between;
}

View file

@ -46,22 +46,7 @@
</div>
<p class="help is-danger" v-if="!!errors.dob">{{ `${errors.dob.message}` }}</p>
</div>
<div style="width:40%">
<figure class="image is-avatar is-1by1 is-light">
<img :src="childValidation.avatar || '/images/default-child-avatar.png'" alt />
</figure>
</div>
<file-select
v-if="!childValidation.avatar"
v-model="childValidation.avatar"
accept="image/*"
lable="Upload a photo (1:1)"
></file-select>
<button
class="button is-rounded is-danger"
v-else
@click="childValidation.avatar = null"
>Cleare image</button>
<ImagePicker ref="imagePicker" />
</form>
</Modal>
</template>
@ -70,14 +55,15 @@
<script lang="ts">
import Modal from "../../shared/components/Modal/Modal.vue";
import Services from "../../services";
import FileSelect from "../../shared/components/FileSelect/FileSelect.vue";
import ImagePicker from "./ImagePicker.vue";
export default {
name: "AddChildModal",
components: {
Modal,
FileSelect
ImagePicker
},
props: ["isActive"],
data() {
return {
errors: {},
@ -98,12 +84,13 @@ export default {
},
async addChild() {
this.errors = {};
this.childValidation.enableInput = false;
const childData = {
name: this.childValidation.name,
dob: this.childValidation.dob,
avatar: this.childValidation.avatar
avatar: this.$refs.imagePicker.generateDataUrl("image/png", 0.8)
};
if (this.$refs.imagePicker.isDefaultImage) delete childData.avatar;
try {
const response = await Services.ApiService.createChild(

View file

@ -0,0 +1,96 @@
<template>
<div class="columns has-text-centered">
<div class="column">
<figure class="image is-avatar is-inline-block is-light">
<croppa
v-model="croppa"
initial-image="/images/default-child-avatar.png"
:prevent-white-space="true"
:show-remove-button="false"
:disable-drag-to-move="isDefaultImage"
@new-image="isDefaultImage=false;zoomState=1"
></croppa>
</figure>
</div>
<div class="column is-flex-column m-t-md m-b-md m-r-lg">
<button
type="button"
class="button is-rounded is-primary"
v-if="true"
@click="croppa.chooseFile()"
>
<i class="fa fa-fw fa-refresh"></i> Change Photo
</button>
<button
type="button"
class="button is-rounded is-danger"
v-if="true"
:disabled="isDefaultImage"
@click="croppa.refresh();isDefaultImage=true"
>
<i class="fa fa-fw fa-trash"></i> Remove Image
</button>
<div class="is-flex">
<button
type="button"
class="button is-rounded is-outlined"
:disabled="isDefaultImage"
@click="zoomState--"
>
<i class="fa fa-fw fa-minus"></i>
</button>
<input type="range" min="1" max="100" v-model="zoomState" :disabled="isDefaultImage" />
<button
type="button"
class="button is-rounded is-outlined"
:disabled="isDefaultImage"
@click="zoomState++"
>
<i class="fa fa-fw fa-plus"></i>
</button>
</div>
</div>
</div>
</template>
<script lang="ts">
import Croppa from "vue-croppa";
export default {
name: "ImagePicker",
components: {
Croppa: Croppa.component
},
watch: {
zoomState: function(newVal, oldVal) {
if (newVal < oldVal) {
this.croppa.zoomOut();
console.log(this.croppa.getChosenFile());
} else {
this.croppa.zoomIn();
}
}
},
created() {
// this.pickerController = {
// generateDataUrl(mimeType: string, compression: number) {
// return this.croppa.generateDataUrl(mimeType, compression);
// }
// };
},
methods: {
generateDataUrl(mimeType: string, compression: number) {
return this.croppa.generateDataUrl(mimeType, compression);
}
},
data() {
return {
zoomState: 1,
isDefaultImage: true,
croppa: {}
};
}
};
</script>

View file

@ -224,17 +224,23 @@ export default {
if (this.childCoverModalImage) {
this.loading = true;
try {
this.child.profile_cover = await Services.ApiService.updateChildCover(
const response = await Services.ApiService.updateChild(
this.child.id,
this.childCoverModalImage
{
profile_cover: this.childCoverModalImage
}
);
if (response.code === 0) {
this.child.profile_cover = response.data.child.profile_cover;
}
} catch (error) {
console.error(error);
console.error(error.message);
}
this.loading = false;
}
this.showCoverModal = false;
this.this.childCoverModalImage = null;
return true;
},
togleEditMode() {
this.inEditMode = !this.inEditMode;

View file

@ -250,9 +250,9 @@ export default {
if (this.childCoverModalImage) {
this.loading = true;
try {
this.child.profile_cover = await Services.ApiService.updateChildCover(
this.child.profile_cover = await Services.ApiService.updateChild(
this.child.id,
this.childCoverModalImage
{ profile_cover: this.childCoverModalImage }
);
} catch (error) {
console.error(error);

View file

@ -44,18 +44,16 @@ export default class ApiService {
}
}
static async updateChildCover(child_id: number, profile_cover: string) {
static async updateChild(child_id: number, data: any) {
const options = {
method: 'POST',
body: JSON.stringify({
profile_cover
}),
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json'
}
};
try {
const response = await fetch(`/api/v1/client/child/${child_id}/profile/cover`, options);
const response = await fetch(`/api/v1/client/child/${child_id}`, options);
console.log(response);
return response.json();

View file

@ -46,9 +46,7 @@ Route
Route.get('user', 'ClientApiController.getUser');
Route.post('child', 'ClientApiController.createChild');
Route.get('child/:id', 'ClientApiController.getChild');
Route.post(
'child/:id/profile/cover',
'ClientApiController.setChildProfileCover');
Route.post('child/:id', 'ClientApiController.updateChild');
Route.post('call/create', 'ClientApiController.createCall');
})
.prefix('api/v1/client')