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; const body = request.body;
if (body.avatar) { if (body.avatar) {
const file = await FileUtils.saveBase64File(body.avatar); const file = await FileUtils.saveBase64File(body.avatar);
console.log(file);
body.avatar = `/u/images/${file.fileName}`; body.avatar = `/u/images/${file.fileName}`;
} else { } else {
body.avatar = `/images/default-child-avatar.png`; 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}) { async setChildProfileCover({request, auth, response}) {
try { try {
const rules = { const rules = {

14
package-lock.json generated
View file

@ -2758,6 +2758,11 @@
"integrity": "sha512-UiRZmBYd1HdVVdFKy7PuLVx9e2NS7SMyx7QpWvFjiklYrLJKpLd19cRnRNqlw4zYa7vVejS3c8JUVobX241zHQ==", "integrity": "sha512-UiRZmBYd1HdVVdFKy7PuLVx9e2NS7SMyx7QpWvFjiklYrLJKpLd19cRnRNqlw4zYa7vVejS3c8JUVobX241zHQ==",
"dev": true "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": { "capture-stack-trace": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", "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", "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.11.tgz",
"integrity": "sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ==" "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": { "vue-hot-reload-api": {
"version": "2.3.4", "version": "2.3.4",
"resolved": "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz", "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", "typescript": "^3.7.5",
"uuid": "^8.0.0", "uuid": "^8.0.0",
"vue": "^2.6.11", "vue": "^2.6.11",
"vue-croppa": "^1.3.8",
"vue-router": "^3.1.5", "vue-router": "^3.1.5",
"vuex": "^3.1.2" "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 */ /*! bulma.io v0.8.2 | MIT License | github.com/jgthms/bulma */
@import url("https://fonts.googleapis.com/css?family=Montserrat&display=swap"); @import url("https://fonts.googleapis.com/css?family=Montserrat&display=swap");
@import url("/vendor/croppa/vue-croppa.min.css");
@keyframes spinAround { @keyframes spinAround {
from { from {
transform: rotate(0deg); } transform: rotate(0deg); }
@ -8079,3 +8080,8 @@ video {
color: whitesmoke; color: whitesmoke;
font-size: 50px; font-size: 50px;
padding-top: 2px; } 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 './mixins.scss';
@import "../../node_modules/bulma/bulma.sass"; @import "../../node_modules/bulma/bulma.sass";
@import "./overrides.scss"; @import "./overrides.scss";
@import url('/vendor/croppa/vue-croppa.min.css');
// @import '../../node_modules/animate.css/source/_base.css'; // @import '../../node_modules/animate.css/source/_base.css';
// nav.navbar { // nav.navbar {
// padding-left: 5rem; // padding-left: 5rem;
@ -457,3 +459,9 @@ video{
padding-top: 2px; padding-top: 2px;
} }
} }
.is-flex-column{
display: flex;
flex-flow: column;
justify-content: space-between;
}

View file

@ -46,22 +46,7 @@
</div> </div>
<p class="help is-danger" v-if="!!errors.dob">{{ `${errors.dob.message}` }}</p> <p class="help is-danger" v-if="!!errors.dob">{{ `${errors.dob.message}` }}</p>
</div> </div>
<div style="width:40%"> <ImagePicker ref="imagePicker" />
<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>
</form> </form>
</Modal> </Modal>
</template> </template>
@ -70,14 +55,15 @@
<script lang="ts"> <script lang="ts">
import Modal from "../../shared/components/Modal/Modal.vue"; import Modal from "../../shared/components/Modal/Modal.vue";
import Services from "../../services"; import Services from "../../services";
import FileSelect from "../../shared/components/FileSelect/FileSelect.vue"; import ImagePicker from "./ImagePicker.vue";
export default { export default {
name: "AddChildModal", name: "AddChildModal",
components: { components: {
Modal, Modal,
FileSelect ImagePicker
}, },
props: ["isActive"], props: ["isActive"],
data() { data() {
return { return {
errors: {}, errors: {},
@ -98,12 +84,13 @@ export default {
}, },
async addChild() { async addChild() {
this.errors = {}; this.errors = {};
this.childValidation.enableInput = false;
const childData = { const childData = {
name: this.childValidation.name, name: this.childValidation.name,
dob: this.childValidation.dob, 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 { try {
const response = await Services.ApiService.createChild( 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) { if (this.childCoverModalImage) {
this.loading = true; this.loading = true;
try { try {
this.child.profile_cover = await Services.ApiService.updateChildCover( const response = await Services.ApiService.updateChild(
this.child.id, this.child.id,
this.childCoverModalImage {
profile_cover: this.childCoverModalImage
}
); );
if (response.code === 0) {
this.child.profile_cover = response.data.child.profile_cover;
}
} catch (error) { } catch (error) {
console.error(error); console.error(error.message);
} }
this.loading = false; this.loading = false;
} }
this.showCoverModal = false; this.showCoverModal = false;
this.this.childCoverModalImage = null; this.this.childCoverModalImage = null;
return true;
}, },
togleEditMode() { togleEditMode() {
this.inEditMode = !this.inEditMode; this.inEditMode = !this.inEditMode;

View file

@ -250,9 +250,9 @@ export default {
if (this.childCoverModalImage) { if (this.childCoverModalImage) {
this.loading = true; this.loading = true;
try { try {
this.child.profile_cover = await Services.ApiService.updateChildCover( this.child.profile_cover = await Services.ApiService.updateChild(
this.child.id, this.child.id,
this.childCoverModalImage { profile_cover: this.childCoverModalImage }
); );
} catch (error) { } catch (error) {
console.error(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 = { const options = {
method: 'POST', method: 'POST',
body: JSON.stringify({ body: JSON.stringify(data),
profile_cover
}),
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
} }
}; };
try { 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); console.log(response);
return response.json(); return response.json();

View file

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