Made an image picker component + more polished create new child
This commit is contained in:
parent
09ff1e38a6
commit
05876f4492
16 changed files with 7523 additions and 363 deletions
|
@ -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
14
package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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
|
@ -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; }
|
||||
|
|
1
public/vendor/croppa/vue-croppa.min.css
vendored
Normal file
1
public/vendor/croppa/vue-croppa.min.css
vendored
Normal 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}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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>
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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')
|
||||
|
|
Reference in a new issue