make indentation consistent
This commit is contained in:
parent
d52575aa42
commit
9f63414ba2
27 changed files with 858 additions and 804 deletions
|
@ -1,15 +1,15 @@
|
|||
<template>
|
||||
<div>
|
||||
<Modal v-if="isModal" title="Add Item" @close="$emit('close')">
|
||||
<template #body>
|
||||
<EditItem :item="item"/>
|
||||
</template>
|
||||
<template #buttons>
|
||||
<button type="button" class="btn btn-secondary" @click="$emit('close')">Cancel</button>
|
||||
<button type="button" class="btn btn-success" @click="saveNewItem()">Save new Item</button>
|
||||
</template>
|
||||
</Modal>
|
||||
</div>
|
||||
<div>
|
||||
<Modal v-if="isModal" title="Add Item" @close="$emit('close')">
|
||||
<template #body>
|
||||
<EditItem :item="item"/>
|
||||
</template>
|
||||
<template #buttons>
|
||||
<button type="button" class="btn btn-secondary" @click="$emit('close')">Cancel</button>
|
||||
<button type="button" class="btn btn-success" @click="saveNewItem()">Save new Item</button>
|
||||
</template>
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -17,22 +17,22 @@ import Modal from '@/components/Modal';
|
|||
import EditItem from '@/components/EditItem';
|
||||
|
||||
export default {
|
||||
name: 'AddItemModal',
|
||||
components: { Modal, EditItem },
|
||||
props: ['isModal'],
|
||||
data: () => ({
|
||||
item: {}
|
||||
}),
|
||||
created() {
|
||||
this.item = {box: this.$store.state.lastUsed.box || '', cid: this.$store.state.lastUsed.cid || ''};
|
||||
},
|
||||
methods: {
|
||||
saveNewItem() {
|
||||
this.$store.dispatch('postItem', this.item).then(() => {
|
||||
this.$emit('close');
|
||||
});
|
||||
name: 'AddItemModal',
|
||||
components: {Modal, EditItem},
|
||||
props: ['isModal'],
|
||||
data: () => ({
|
||||
item: {}
|
||||
}),
|
||||
created() {
|
||||
this.item = {box: this.$store.state.lastUsed.box || '', cid: this.$store.state.lastUsed.cid || ''};
|
||||
},
|
||||
methods: {
|
||||
saveNewItem() {
|
||||
this.$store.dispatch('postItem', this.item).then(() => {
|
||||
this.$emit('close');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
@ -22,27 +22,27 @@
|
|||
:value="filters[column]"
|
||||
@input="changeFilter(column, $event.target.value)"
|
||||
>
|
||||
<!-- <input @change="someHandler"> -->
|
||||
<!-- <input @change="someHandler"> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-9 col-xl-8">
|
||||
<div class="row">
|
||||
<div
|
||||
class="mb-4 col-lg-4 col-xl-3"
|
||||
v-for="item in internalItems"
|
||||
:key="item[keyName]"
|
||||
>
|
||||
<div
|
||||
class="card-list-item card bg-dark text-light"
|
||||
@click="$emit('itemActivated', item)"
|
||||
>
|
||||
<slot v-bind:item="item"/>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div
|
||||
class="mb-4 col-lg-4 col-xl-3"
|
||||
v-for="item in internalItems"
|
||||
:key="item[keyName]"
|
||||
>
|
||||
<div
|
||||
class="card-list-item card bg-dark text-light"
|
||||
@click="$emit('itemActivated', item)"
|
||||
>
|
||||
<slot v-bind:item="item"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -52,17 +52,23 @@ import DataContainer from '@/mixins/data-container';
|
|||
import router from '../router';
|
||||
|
||||
export default {
|
||||
name: 'Cards',
|
||||
mixins: [DataContainer],
|
||||
created() {
|
||||
this.columns.map(e => ({k: e, v: this.$store.getters.getFilters[e]})).filter(e => e.v).forEach(e => this.setFilter(e.k, e.v));
|
||||
},
|
||||
methods: {
|
||||
changeFilter(col, val) {
|
||||
this.setFilter(col, val);
|
||||
let newquery = Object.entries({...this.$store.getters.getFilters, [col]: val}).reduce((a,[k,v]) => (v ? {...a, [k]:v} : a), {});
|
||||
router.push({query: newquery});
|
||||
name: 'Cards',
|
||||
mixins: [DataContainer],
|
||||
created() {
|
||||
this.columns.map(e => ({
|
||||
k: e,
|
||||
v: this.$store.getters.getFilters[e]
|
||||
})).filter(e => e.v).forEach(e => this.setFilter(e.k, e.v));
|
||||
},
|
||||
methods: {
|
||||
changeFilter(col, val) {
|
||||
this.setFilter(col, val);
|
||||
let newquery = Object.entries({
|
||||
...this.$store.getters.getFilters,
|
||||
[col]: val
|
||||
}).reduce((a, [k, v]) => (v ? {...a, [k]: v} : a), {});
|
||||
router.push({query: newquery});
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -25,23 +25,23 @@
|
|||
<script>
|
||||
import InputString from './inputs/InputString';
|
||||
import InputCombo from './inputs/InputCombo';
|
||||
import { mapGetters } from 'vuex';
|
||||
import {mapGetters} from 'vuex';
|
||||
import InputPhoto from './inputs/InputPhoto';
|
||||
|
||||
export default {
|
||||
name: 'EditItem',
|
||||
components: {InputPhoto, InputCombo, InputString },
|
||||
props: ['item'],
|
||||
computed: {
|
||||
...mapGetters(['getBoxes']),
|
||||
boxes({ getBoxes }) {
|
||||
return getBoxes.map(obj => ({cid: obj.cid, box: obj.name}));
|
||||
name: 'EditItem',
|
||||
components: {InputPhoto, InputCombo, InputString},
|
||||
props: ['item'],
|
||||
computed: {
|
||||
...mapGetters(['getBoxes']),
|
||||
boxes({getBoxes}) {
|
||||
return getBoxes.map(obj => ({cid: obj.cid, box: obj.name}));
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
storeImage(image) {
|
||||
this.item.dataImage = image;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
storeImage(image) {
|
||||
this.item.dataImage = image;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -19,18 +19,18 @@ import Modal from '@/components/Modal';
|
|||
import config from '../config';
|
||||
|
||||
export default {
|
||||
name: 'Lightbox',
|
||||
components: { Modal },
|
||||
props: ['file'],
|
||||
data: ()=>({
|
||||
baseUrl: config.service.url,
|
||||
}),
|
||||
name: 'Lightbox',
|
||||
components: {Modal},
|
||||
props: ['file'],
|
||||
data: () => ({
|
||||
baseUrl: config.service.url,
|
||||
}),
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#lightbox-image {
|
||||
#lightbox-image {
|
||||
max-height: 75vh;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -29,47 +29,47 @@
|
|||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Modal',
|
||||
props: ['title'],
|
||||
mounted() {
|
||||
this.$el.focus();
|
||||
}
|
||||
name: 'Modal',
|
||||
props: ['title'],
|
||||
mounted() {
|
||||
this.$el.focus();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.modal {
|
||||
background-color: rgba(0,0,0,0.4); /* Transparent dimmed overlay */
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: table !important;
|
||||
}
|
||||
.modal {
|
||||
background-color: rgba(0, 0, 0, 0.4); /* Transparent dimmed overlay */
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: table !important;
|
||||
}
|
||||
|
||||
.modal.hidden {
|
||||
display: none;
|
||||
}
|
||||
.modal.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.modal .container {
|
||||
display: table-cell;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
width: 200px;
|
||||
}
|
||||
.modal .container {
|
||||
display: table-cell;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.modal .body {
|
||||
box-shadow: 5px 10px #888888;
|
||||
display: inline-block;
|
||||
background-color: white;
|
||||
border: 1px solid black;
|
||||
padding: 10px;
|
||||
}
|
||||
.modal .body {
|
||||
box-shadow: 5px 10px #888888;
|
||||
display: inline-block;
|
||||
background-color: white;
|
||||
border: 1px solid black;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* Hacky Fix, for this Issue: https://hannover.ccc.de/gitlab/c3lf/lffrontend/issues/26 */
|
||||
/*.modal-body {
|
||||
max-height: calc(100vh - 200px);
|
||||
overflow-y: auto;
|
||||
}*/
|
||||
/* Hacky Fix, for this Issue: https://hannover.ccc.de/gitlab/c3lf/lffrontend/issues/26 */
|
||||
/*.modal-body {
|
||||
max-height: calc(100vh - 200px);
|
||||
overflow-y: auto;
|
||||
}*/
|
||||
</style>
|
|
@ -3,7 +3,7 @@
|
|||
<div class="dropdown">
|
||||
<button class="btn text-light dropdown-toggle btn-heading" type="button" id="dropdownMenuButton"
|
||||
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
{{getEventSlug}}
|
||||
{{ getEventSlug }}
|
||||
</button>
|
||||
<div class="dropdown-menu bg-dark" aria-labelledby="dropdownMenuButton">
|
||||
<a class="dropdown-item text-light" href="#" v-for="(event, index) in events" v-bind:key="index"
|
||||
|
@ -13,7 +13,8 @@
|
|||
|
||||
<div class="custom-control-inline mr-1">
|
||||
<button type="button" class="btn mx-1 text-nowrap btn-success" @click="$emit('addClicked')">
|
||||
<font-awesome-icon icon="plus"/><span class="d-none d-md-inline"> Add</span>
|
||||
<font-awesome-icon icon="plus"/>
|
||||
<span class="d-none d-md-inline"> Add</span>
|
||||
</button>
|
||||
<div class="btn-group btn-group-toggle">
|
||||
<button :class="['btn', 'btn-info', { active: layout === 'cards' }]" @click="setLayout('cards')">
|
||||
|
@ -32,13 +33,13 @@
|
|||
|
||||
<form class="form-inline mt-1 my-lg-auto my-xl-auto w-100 d-inline">
|
||||
<input
|
||||
class="form-control w-100"
|
||||
type="search"
|
||||
placeholder="Search"
|
||||
aria-label="Search"
|
||||
v-debounce:500ms="myFunc"
|
||||
@input="searchEventItems($event.target.value)"
|
||||
disabled
|
||||
class="form-control w-100"
|
||||
type="search"
|
||||
placeholder="Search"
|
||||
aria-label="Search"
|
||||
v-debounce:500ms="myFunc"
|
||||
@input="searchEventItems($event.target.value)"
|
||||
disabled
|
||||
>
|
||||
</form>
|
||||
|
||||
|
@ -47,10 +48,11 @@
|
|||
<li class="nav-item dropdown">
|
||||
<button class="btn nav-link dropdown-toggle" type="button" id="dropdownMenuButton2"
|
||||
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
{{getActiveView}}
|
||||
{{ getActiveView }}
|
||||
</button>
|
||||
<ul class="dropdown-menu bg-dark" aria-labelledby="dropdownMenuButton2">
|
||||
<li class="" v-for="(link, index) in views" v-bind:key="index" :class="{ active: link.path === getActiveView }">
|
||||
<li class="" v-for="(link, index) in views" v-bind:key="index"
|
||||
:class="{ active: link.path === getActiveView }">
|
||||
<a class="nav-link text-nowrap" href="#" @click="changeView(link)">{{ link.title }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -66,31 +68,31 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapActions, mapMutations, mapGetters } from 'vuex';
|
||||
import {mapState, mapActions, mapMutations, mapGetters} from 'vuex';
|
||||
|
||||
export default {
|
||||
name: 'Navbar',
|
||||
data: () => ({
|
||||
views: [
|
||||
{'title':'items','path':'items'},
|
||||
{'title':'boxes','path':'boxes'},
|
||||
//{'title':'mass-edit','path':'massedit'},
|
||||
],
|
||||
links: [
|
||||
{'title':'howto engel','path':'/howto/'}
|
||||
]
|
||||
}),
|
||||
computed: {
|
||||
...mapState(['events', 'activeEvent', 'layout']),
|
||||
...mapGetters(['getEventSlug', 'getActiveView']),
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['changeEvent', 'changeView','searchEventItems']),
|
||||
...mapMutations(['setLayout'])
|
||||
}
|
||||
name: 'Navbar',
|
||||
data: () => ({
|
||||
views: [
|
||||
{'title': 'items', 'path': 'items'},
|
||||
{'title': 'boxes', 'path': 'boxes'},
|
||||
//{'title':'mass-edit','path':'massedit'},
|
||||
],
|
||||
links: [
|
||||
{'title': 'howto engel', 'path': '/howto/'}
|
||||
]
|
||||
}),
|
||||
computed: {
|
||||
...mapState(['events', 'activeEvent', 'layout']),
|
||||
...mapGetters(['getEventSlug', 'getActiveView']),
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['changeEvent', 'changeView', 'searchEventItems']),
|
||||
...mapMutations(['setLayout'])
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "../scss/navbar.scss";
|
||||
@import "../scss/navbar.scss";
|
||||
</style>
|
|
@ -1,39 +1,39 @@
|
|||
<template>
|
||||
<table class="table table-striped table-dark">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" v-for="(column, index) in columns" :key="index">
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<button
|
||||
:class="[ 'btn', column === sortBy ? 'btn-outline-info' : 'btn-outline-secondary' ]"
|
||||
@click="toggleSort(column)"
|
||||
>
|
||||
{{ column }}
|
||||
<span :class="{ 'text-info': column === sortBy }">
|
||||
<tr>
|
||||
<th scope="col" v-for="(column, index) in columns" :key="index">
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<button
|
||||
:class="[ 'btn', column === sortBy ? 'btn-outline-info' : 'btn-outline-secondary' ]"
|
||||
@click="toggleSort(column)"
|
||||
>
|
||||
{{ column }}
|
||||
<span :class="{ 'text-info': column === sortBy }">
|
||||
<font-awesome-icon :icon="getSortIcon(column)"/>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="filter"
|
||||
:value="filters[column]"
|
||||
@input="changeFilter(column, $event.target.value)"
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="filter"
|
||||
:value="filters[column]"
|
||||
@input="changeFilter(column, $event.target.value)"
|
||||
>
|
||||
</div>
|
||||
</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="item in internalItems" :key="item[keyName]" @click="$emit('itemActivated', item)">
|
||||
<td v-for="(column, index) in columns" :key="index">{{ item[column] }}</td>
|
||||
<td>
|
||||
<slot v-bind:item="item"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-for="item in internalItems" :key="item[keyName]" @click="$emit('itemActivated', item)">
|
||||
<td v-for="(column, index) in columns" :key="index">{{ item[column] }}</td>
|
||||
<td>
|
||||
<slot v-bind:item="item"/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
@ -43,23 +43,29 @@ import DataContainer from '@/mixins/data-container';
|
|||
import router from '../router';
|
||||
|
||||
export default {
|
||||
name: 'Table',
|
||||
mixins: [DataContainer],
|
||||
created() {
|
||||
this.columns.map(e => ({k: e, v: this.$store.getters.getFilters[e]})).filter(e => e.v).forEach(e => this.setFilter(e.k, e.v));
|
||||
},
|
||||
methods: {
|
||||
changeFilter(col, val) {
|
||||
this.setFilter(col, val);
|
||||
let newquery = Object.entries({...this.$store.getters.getFilters, [col]: val}).reduce((a,[k,v]) => (v ? {...a, [k]:v} : a), {});
|
||||
router.push({query: newquery});
|
||||
name: 'Table',
|
||||
mixins: [DataContainer],
|
||||
created() {
|
||||
this.columns.map(e => ({
|
||||
k: e,
|
||||
v: this.$store.getters.getFilters[e]
|
||||
})).filter(e => e.v).forEach(e => this.setFilter(e.k, e.v));
|
||||
},
|
||||
methods: {
|
||||
changeFilter(col, val) {
|
||||
this.setFilter(col, val);
|
||||
let newquery = Object.entries({
|
||||
...this.$store.getters.getFilters,
|
||||
[col]: val
|
||||
}).reduce((a, [k, v]) => (v ? {...a, [k]: v} : a), {});
|
||||
router.push({query: newquery});
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.table-body-move {
|
||||
transition: transform 1s;
|
||||
}
|
||||
.table-body-move {
|
||||
transition: transform 1s;
|
||||
}
|
||||
</style>
|
|
@ -14,35 +14,35 @@
|
|||
<script>
|
||||
import $ from 'jquery';
|
||||
import 'bootstrap/js/dist/toast';
|
||||
import { DateTime } from 'luxon';
|
||||
import {DateTime} from 'luxon';
|
||||
|
||||
export default {
|
||||
name: 'Toast',
|
||||
props: ['title', 'message', 'color'],
|
||||
data: () => ({
|
||||
creationTime: DateTime.local(),
|
||||
displayTime: 'just now',
|
||||
timer: undefined
|
||||
}),
|
||||
mounted() {
|
||||
const { toast } = this.$refs;
|
||||
$(toast).toast('show');
|
||||
this.timer = setInterval(this.updateDisplayTime, 1000);
|
||||
},
|
||||
methods: {
|
||||
close() {
|
||||
const { toast } = this.$refs;
|
||||
$(toast).toast('hide');
|
||||
window.setTimeout(() => {
|
||||
this.$emit('close');
|
||||
}, 500);
|
||||
name: 'Toast',
|
||||
props: ['title', 'message', 'color'],
|
||||
data: () => ({
|
||||
creationTime: DateTime.local(),
|
||||
displayTime: 'just now',
|
||||
timer: undefined
|
||||
}),
|
||||
mounted() {
|
||||
const {toast} = this.$refs;
|
||||
$(toast).toast('show');
|
||||
this.timer = setInterval(this.updateDisplayTime, 1000);
|
||||
},
|
||||
updateDisplayTime() {
|
||||
this.displayTime = this.creationTime.toRelative();
|
||||
methods: {
|
||||
close() {
|
||||
const {toast} = this.$refs;
|
||||
$(toast).toast('hide');
|
||||
window.setTimeout(() => {
|
||||
this.$emit('close');
|
||||
}, 500);
|
||||
},
|
||||
updateDisplayTime() {
|
||||
this.displayTime = this.creationTime.toRelative();
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
clearInterval(this.timer);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
clearInterval(this.timer);
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -1,22 +1,22 @@
|
|||
<template>
|
||||
<div class="input-group-append">
|
||||
<div class="input-group-append">
|
||||
<span
|
||||
class="input-group-text"
|
||||
data-toggle="tooltip"
|
||||
data-placement="top"
|
||||
:title="type"
|
||||
>{{ type[0] }}</span>
|
||||
<button
|
||||
type="button"
|
||||
class="btn"
|
||||
:class="'btn-' + color"
|
||||
data-toggle="tooltip"
|
||||
data-placement="top"
|
||||
:title="tooltip"
|
||||
>
|
||||
<font-awesome-icon :icon="icon"/>
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn"
|
||||
:class="'btn-' + color"
|
||||
data-toggle="tooltip"
|
||||
data-placement="top"
|
||||
:title="tooltip"
|
||||
>
|
||||
<font-awesome-icon :icon="icon"/>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -24,27 +24,27 @@ import $ from 'jquery';
|
|||
import 'bootstrap/js/dist/tooltip';
|
||||
|
||||
export default {
|
||||
name: 'Addon',
|
||||
props: [ 'type', 'isValid' ],
|
||||
mounted() {
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
},
|
||||
computed: {
|
||||
icon() {
|
||||
if (this.isValid == undefined) return 'pen';
|
||||
if (this.isValid == false) return 'times';
|
||||
return 'check';
|
||||
name: 'Addon',
|
||||
props: ['type', 'isValid'],
|
||||
mounted() {
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
},
|
||||
color() {
|
||||
if (this.isValid == undefined) return 'info';
|
||||
if (this.isValid == false) return 'danger';
|
||||
return 'success';
|
||||
},
|
||||
tooltip() {
|
||||
if (this.isValid == undefined) return 'no data validation';
|
||||
if (this.isValid == false) return 'data invalid';
|
||||
return 'data valid';
|
||||
computed: {
|
||||
icon() {
|
||||
if (this.isValid == undefined) return 'pen';
|
||||
if (this.isValid == false) return 'times';
|
||||
return 'check';
|
||||
},
|
||||
color() {
|
||||
if (this.isValid == undefined) return 'info';
|
||||
if (this.isValid == false) return 'danger';
|
||||
return 'success';
|
||||
},
|
||||
tooltip() {
|
||||
if (this.isValid == undefined) return 'no data validation';
|
||||
if (this.isValid == false) return 'data invalid';
|
||||
return 'data valid';
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
class="btn btn-outline-secondary dropdown-toggle"
|
||||
type="button"
|
||||
data-toggle="dropdown"
|
||||
>Search</button>
|
||||
>Search
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
<a
|
||||
v-for="(option, index) in sortedOptions"
|
||||
|
@ -16,20 +17,20 @@
|
|||
@click="setInternalValue(option)"
|
||||
:class="{ active: option == selectedOption }"
|
||||
>
|
||||
{{ option[nameKey] }}
|
||||
{{ option[nameKey] }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<input type="text" class="form-control" :id="label" v-model="internalName">
|
||||
<div class="input-group-append">
|
||||
<button
|
||||
class="btn"
|
||||
:class="{ 'btn-info disabled': isValid, 'btn-success': !isValid }"
|
||||
v-if="!isValid"
|
||||
@click="addOption()"
|
||||
>
|
||||
<font-awesome-icon icon="plus"/>
|
||||
</button>
|
||||
<button
|
||||
class="btn"
|
||||
:class="{ 'btn-info disabled': isValid, 'btn-success': !isValid }"
|
||||
v-if="!isValid"
|
||||
@click="addOption()"
|
||||
>
|
||||
<font-awesome-icon icon="plus"/>
|
||||
</button>
|
||||
</div>
|
||||
<Addon type="Combo Box" :is-valid="isValid"/>
|
||||
</div>
|
||||
|
@ -40,37 +41,40 @@
|
|||
import Addon from './Addon';
|
||||
|
||||
export default {
|
||||
name: 'InputCombo',
|
||||
components: {Addon},
|
||||
props: [ 'label', 'model', 'nameKey', 'uniqueKey', 'options', 'onOptionAdd' ],
|
||||
data: ({ options, model, nameKey, uniqueKey }) => ({
|
||||
internalName: model[nameKey],
|
||||
selectedOption: options.filter(e => e[uniqueKey] == model[uniqueKey])[0],
|
||||
addingOption: false
|
||||
}),
|
||||
computed: {
|
||||
isValid: ({options, nameKey, internalName}) => options.some(e => e[nameKey] == internalName),
|
||||
sortedOptions: ({options, nameKey}) => options.sort((a, b) => a[nameKey].localeCompare(b[nameKey], 'en', { numeric: true })),
|
||||
},
|
||||
watch: {
|
||||
internalName(newValue) {
|
||||
if (this.isValid) {
|
||||
if(!this.selectedOption || newValue!=this.selectedOption[this.nameKey]){
|
||||
this.selectedOption = this.options.filter(e => e[this.nameKey] === newValue)[0];
|
||||
}
|
||||
this.model[this.nameKey] = this.selectedOption[this.nameKey];
|
||||
this.model[this.uniqueKey] = this.selectedOption[this.uniqueKey];
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setInternalValue(option) {
|
||||
this.selectedOption = option;
|
||||
this.internalName = option[this.nameKey];
|
||||
name: 'InputCombo',
|
||||
components: {Addon},
|
||||
props: ['label', 'model', 'nameKey', 'uniqueKey', 'options', 'onOptionAdd'],
|
||||
data: ({options, model, nameKey, uniqueKey}) => ({
|
||||
internalName: model[nameKey],
|
||||
selectedOption: options.filter(e => e[uniqueKey] == model[uniqueKey])[0],
|
||||
addingOption: false
|
||||
}),
|
||||
computed: {
|
||||
isValid: ({options, nameKey, internalName}) => options.some(e => e[nameKey] == internalName),
|
||||
sortedOptions: ({
|
||||
options,
|
||||
nameKey
|
||||
}) => options.sort((a, b) => a[nameKey].localeCompare(b[nameKey], 'en', {numeric: true})),
|
||||
},
|
||||
addOption() {
|
||||
this.onOptionAdd({[this.nameKey]: this.internalName});
|
||||
watch: {
|
||||
internalName(newValue) {
|
||||
if (this.isValid) {
|
||||
if (!this.selectedOption || newValue != this.selectedOption[this.nameKey]) {
|
||||
this.selectedOption = this.options.filter(e => e[this.nameKey] === newValue)[0];
|
||||
}
|
||||
this.model[this.nameKey] = this.selectedOption[this.nameKey];
|
||||
this.model[this.uniqueKey] = this.selectedOption[this.uniqueKey];
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setInternalValue(option) {
|
||||
this.selectedOption = option;
|
||||
this.internalName = option[this.nameKey];
|
||||
},
|
||||
addOption() {
|
||||
this.onOptionAdd({[this.nameKey]: this.internalName});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
</label>
|
||||
<input type="file" id="file" accept="image/png" class="d-none" @change="onFileChange($event)">
|
||||
<button v-if="!capturing" class="btn my-2 ml-auto btn-secondary" @click="openStream()">
|
||||
<font-awesome-icon icon="camera"/>
|
||||
<font-awesome-icon icon="camera"/>
|
||||
</button>
|
||||
<div v-if="capturing" class="btn-group my-2 ml-auto">
|
||||
<button class="btn btn-success" @click="captureVideoImage()">
|
||||
|
@ -44,80 +44,84 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { mapMutations } from 'vuex';
|
||||
import {mapMutations} from 'vuex';
|
||||
|
||||
export default {
|
||||
name: 'InputPhoto',
|
||||
props: [ 'model', 'field', 'onCapture' ],
|
||||
data: () => ({
|
||||
capturing: false,
|
||||
streaming: false,
|
||||
stream: undefined,
|
||||
dataImage: undefined
|
||||
}),
|
||||
methods: {
|
||||
...mapMutations(['createToast']),
|
||||
openStream() {
|
||||
if (!this.capturing) {
|
||||
this.capturing = true;
|
||||
this.streaming = false;
|
||||
navigator.mediaDevices.getUserMedia({video: { facingMode: "environment" }, audio: false}).then(stream => {
|
||||
this.stream = stream;
|
||||
const { video } = this.$refs;
|
||||
video.srcObject = stream;
|
||||
video.play();
|
||||
video.addEventListener('canplay', () => {
|
||||
this.streaming = true;
|
||||
}, false);
|
||||
}).catch(err => console.log(err)); // todo: toast error
|
||||
}
|
||||
name: 'InputPhoto',
|
||||
props: ['model', 'field', 'onCapture'],
|
||||
data: () => ({
|
||||
capturing: false,
|
||||
streaming: false,
|
||||
stream: undefined,
|
||||
dataImage: undefined
|
||||
}),
|
||||
methods: {
|
||||
...mapMutations(['createToast']),
|
||||
openStream() {
|
||||
if (!this.capturing) {
|
||||
this.capturing = true;
|
||||
this.streaming = false;
|
||||
navigator.mediaDevices.getUserMedia({video: {facingMode: "environment"}, audio: false}).then(stream => {
|
||||
this.stream = stream;
|
||||
const {video} = this.$refs;
|
||||
video.srcObject = stream;
|
||||
video.play();
|
||||
video.addEventListener('canplay', () => {
|
||||
this.streaming = true;
|
||||
}, false);
|
||||
}).catch(err => console.log(err)); // todo: toast error
|
||||
}
|
||||
},
|
||||
captureVideoImage() {
|
||||
const {video, canvas} = this.$refs;
|
||||
const context = canvas.getContext('2d');
|
||||
const {videoWidth, videoHeight} = video;
|
||||
canvas.width = videoWidth;
|
||||
canvas.height = videoHeight;
|
||||
context.drawImage(video, 0, 0, videoWidth, videoHeight);
|
||||
this.dataImage = canvas.toDataURL('image/png');
|
||||
this.onCapture(this.dataImage);
|
||||
this.closeStream();
|
||||
},
|
||||
closeStream() {
|
||||
if (this.capturing) {
|
||||
this.stream.getTracks().forEach(s => s.stop());
|
||||
this.capturing = false;
|
||||
this.streaming = false;
|
||||
}
|
||||
},
|
||||
onFileChange({target}) {
|
||||
const file = target.files[0];
|
||||
var reader = new FileReader();
|
||||
reader.readAsDataURL(file);
|
||||
const self = this;
|
||||
reader.onload = function () {
|
||||
self.dataImage = reader.result;
|
||||
self.onCapture(self.dataImage);
|
||||
self.closeStream();
|
||||
};
|
||||
reader.onerror = function (error) {
|
||||
this.createToast({
|
||||
title: 'Error: Failed to parse image file',
|
||||
message: error.toString(),
|
||||
color: 'danger'
|
||||
});
|
||||
console.log('Error: ', error);
|
||||
};
|
||||
}
|
||||
},
|
||||
captureVideoImage() {
|
||||
const { video, canvas } = this.$refs;
|
||||
const context = canvas.getContext('2d');
|
||||
const { videoWidth, videoHeight } = video;
|
||||
canvas.width = videoWidth;
|
||||
canvas.height = videoHeight;
|
||||
context.drawImage(video, 0, 0, videoWidth, videoHeight);
|
||||
this.dataImage = canvas.toDataURL('image/png');
|
||||
this.onCapture(this.dataImage);
|
||||
this.closeStream();
|
||||
mounted() {
|
||||
if (!this.model[this.field])
|
||||
this.openStream();
|
||||
},
|
||||
closeStream() {
|
||||
if (this.capturing) {
|
||||
this.stream.getTracks().forEach(s => s.stop());
|
||||
this.capturing = false;
|
||||
this.streaming = false;
|
||||
}
|
||||
},
|
||||
onFileChange({ target }) {
|
||||
const file = target.files[0];
|
||||
var reader = new FileReader();
|
||||
reader.readAsDataURL(file);
|
||||
const self = this;
|
||||
reader.onload = function () {
|
||||
self.dataImage = reader.result;
|
||||
self.onCapture(self.dataImage);
|
||||
self.closeStream();
|
||||
};
|
||||
reader.onerror = function (error) {
|
||||
this.createToast({ title: 'Error: Failed to parse image file', message: error.toString(), color: 'danger' });
|
||||
console.log('Error: ', error);
|
||||
};
|
||||
beforeDestroy() {
|
||||
this.closeStream();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (!this.model[this.field])
|
||||
this.openStream();
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.closeStream();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.img-preview{
|
||||
.img-preview {
|
||||
max-height: 30vh;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -13,17 +13,17 @@
|
|||
import Addon from './Addon';
|
||||
|
||||
export default {
|
||||
name: 'InputString',
|
||||
components: { Addon },
|
||||
props: [ 'label', 'model', 'field', 'validationFn' ],
|
||||
computed: {
|
||||
hasValidationFn({ validationFn }) {
|
||||
return validationFn != undefined;
|
||||
},
|
||||
isValid({ model, field, validationFn, hasValidationFn }) {
|
||||
if (!hasValidationFn) return true;
|
||||
return validationFn(model[field]);
|
||||
name: 'InputString',
|
||||
components: {Addon},
|
||||
props: ['label', 'model', 'field', 'validationFn'],
|
||||
computed: {
|
||||
hasValidationFn({validationFn}) {
|
||||
return validationFn != undefined;
|
||||
},
|
||||
isValid({model, field, validationFn, hasValidationFn}) {
|
||||
if (!hasValidationFn) return true;
|
||||
return validationFn(model[field]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
Loading…
Add table
Add a link
Reference in a new issue