stash
This commit is contained in:
parent
63d6b7a5a8
commit
0dea5ff451
36 changed files with 1027 additions and 18 deletions
|
@ -1,6 +1,31 @@
|
|||
<template>
|
||||
<div class="row">
|
||||
<div class="col-lg-3 col-xl-2">
|
||||
<!--div class="card bg-dark text-light mb-2" id="filters">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title text-info">Sort & Filter</h5>
|
||||
<div class="form-group" v-for="(column, index) in columns" :key="index">
|
||||
<label>{{ column }}</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<button
|
||||
:class="[ 'btn', column === sortBy ? 'btn-outline-info' : 'btn-outline-secondary' ]"
|
||||
type="button"
|
||||
@click="toggleSort(column)">
|
||||
<font-awesome-icon :icon="getSortIcon(column)"/>
|
||||
</button>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="filter"
|
||||
:value="filters[column]"
|
||||
@input="changeFilter(column, $event.target.value)"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div-->
|
||||
</div>
|
||||
<div class="col-lg-9 col-xl-8">
|
||||
<div class="w-100"
|
||||
|
@ -58,6 +83,13 @@ export default {
|
|||
} else {
|
||||
this.collapsed = this.sections.map(() => true);
|
||||
}
|
||||
|
||||
//this.$router.push({...this.$router.currentRoute, query: {...this.$router.currentRoute.query, layout}});
|
||||
//this.collapsed = this.sections.map(() => true);
|
||||
/*this.columns.map(e => ({
|
||||
k: e,
|
||||
v: this.$store.getters.getFilters[e]
|
||||
})).filter(e => e.v).forEach(e => this.setFilter(e.k, e.v));*/
|
||||
},
|
||||
computed: {
|
||||
grouped_items() {
|
||||
|
|
105
web/src/components/SlotTable.vue
Normal file
105
web/src/components/SlotTable.vue
Normal file
|
@ -0,0 +1,105 @@
|
|||
<template>
|
||||
<table class="table table-striped table-dark">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" v-for="(column, index) in columns" :key="index"
|
||||
v-if="columnHasData[index]||columnHasSlot[index]">
|
||||
<div class="input-group" v-if="columnHasData[index]">
|
||||
<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)"
|
||||
>
|
||||
</div>
|
||||
<span v-else-if="columnHasSlot[index]">
|
||||
{{ column }}
|
||||
</span>
|
||||
</th>
|
||||
<th>
|
||||
<slot name="header_actions"/>
|
||||
</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" v-if="columnHasSlot[index]||columnHasData[index]">
|
||||
<slot v-if="columnHasSlot[index]" :name="column" :item="item"/>
|
||||
<span v-else-if="columnHasData[index]">
|
||||
{{ item[column] }}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ column }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<slot v-bind:item="item" name="actions"/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DataContainer from '@/mixins/data-container';
|
||||
import router from '../router';
|
||||
|
||||
export default {
|
||||
name: 'SlotTable',
|
||||
mixins: [DataContainer],
|
||||
data() {
|
||||
return {
|
||||
columnHasSlot: [],
|
||||
columnHasData: []
|
||||
}
|
||||
},
|
||||
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));
|
||||
|
||||
},
|
||||
mounted() {
|
||||
this.columnHasSlot = this.columns.map(e => Object.keys(this.$slots).includes(e));
|
||||
this.columnHasData = this.columns.map(e => this.items.reduce((a, b) => a || b[e] !== undefined, false));
|
||||
//console.log(this.columnHasData, this.columnHasSlot, this.columns, Object.keys(this.$slots), this.$slots);
|
||||
for (let slot in this.$slots) {
|
||||
console.log(`Slot: ${slot}`);
|
||||
console.log(`Data: ${this.$slots[slot]}`);
|
||||
}
|
||||
},
|
||||
beforeUpdate() {
|
||||
this.columnHasSlot = this.columns.map(e => Object.keys(this.$slots).includes(e));
|
||||
this.columnHasData = this.columns.map(e => this.items.reduce((a, b) => a || b[e] !== undefined, false));
|
||||
},
|
||||
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;
|
||||
}
|
||||
</style>
|
48
web/src/components/Toast.vue
Normal file
48
web/src/components/Toast.vue
Normal file
|
@ -0,0 +1,48 @@
|
|||
<template>
|
||||
<div class="toast" :class="color && ('border-' + color)" role="alert" ref="toast" data-autohide="false">
|
||||
<div class="toast-header" :class="[color && ('bg-' + color), color && 'text-light']">
|
||||
<strong class="mr-auto pr-3">{{ title }}</strong>
|
||||
<small>{{ displayTime }}</small>
|
||||
<button type="button" class="ml-2 mb-1 close" @click="close()">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<!--div class="toast-body" v-html="message">{{ message }}</div-->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import $ from 'jquery';
|
||||
import 'bootstrap/js/dist/toast';
|
||||
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);
|
||||
},
|
||||
updateDisplayTime() {
|
||||
this.displayTime = this.creationTime.toRelative();
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
clearInterval(this.timer);
|
||||
}
|
||||
};
|
||||
</script>
|
112
web/src/components/inputs/FormatedText.vue
Normal file
112
web/src/components/inputs/FormatedText.vue
Normal file
|
@ -0,0 +1,112 @@
|
|||
<template>
|
||||
<div contenteditable @input="onchange" ref="text">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'FormatedText',
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
format: {
|
||||
type: Function,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selection: {start: 0, end: 0, direction: 'forward', type: 'Caret'}
|
||||
};
|
||||
},
|
||||
emits: ['input'],
|
||||
methods: {
|
||||
rawhtml(value) {
|
||||
if (typeof this.format === 'function') {
|
||||
return this.format(value.replace(/ /g, ' '));
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
},
|
||||
onchange(event) {
|
||||
const div = this.$refs.text;
|
||||
const sel = window.getSelection();
|
||||
if (sel.rangeCount > 0) {
|
||||
this.selection.start = this.calculateOffset(div, sel.anchorNode, sel.anchorOffset);
|
||||
this.selection.end = this.calculateOffset(div, sel.focusNode, sel.focusOffset);
|
||||
this.selection.direction = sel.direction;
|
||||
this.selection.type = sel.type;
|
||||
}
|
||||
this.$emit('input', event.target.innerText.replace(/ /g, ' ').replace(/\xA0/g, ' '));
|
||||
},
|
||||
calculateOffset(container, node, offset) {
|
||||
let position = 0;
|
||||
let found = false;
|
||||
const walk = (elem) => {
|
||||
if (elem === node) {
|
||||
found = true;
|
||||
return;
|
||||
}
|
||||
if (elem.nodeType === 3) {
|
||||
position += elem.length;
|
||||
} else {
|
||||
for (let i = 0; i < elem.childNodes.length; i++) {
|
||||
walk(elem.childNodes[i]);
|
||||
if (found) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
walk(container);
|
||||
return position + offset;
|
||||
},
|
||||
findNode(container, offset) {
|
||||
let position = 0;
|
||||
let found = false;
|
||||
let node = null;
|
||||
const walk = (elem) => {
|
||||
if (position + elem.length >= offset) {
|
||||
found = true;
|
||||
node = elem;
|
||||
return;
|
||||
}
|
||||
if (elem.nodeType === 3) {
|
||||
position += elem.length;
|
||||
} else {
|
||||
for (let i = 0; i < elem.childNodes.length; i++) {
|
||||
walk(elem.childNodes[i]);
|
||||
if (found) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
walk(container);
|
||||
return [node, offset - position]
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
value() {
|
||||
if (this.selection) {
|
||||
const div = this.$refs.text;
|
||||
div.innerHTML = this.rawhtml(this.value);
|
||||
|
||||
const range = document.createRange();
|
||||
const sel = window.getSelection();
|
||||
range.setStart(...this.findNode(div, this.selection.start));
|
||||
range.setEnd(...this.findNode(div, this.selection.end));
|
||||
|
||||
sel.removeAllRanges();
|
||||
sel.addRange(range);
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const div = this.$refs.text;
|
||||
div.innerHTML = this.rawhtml(this.value);
|
||||
}
|
||||
};
|
||||
</script>
|
Loading…
Add table
Add a link
Reference in a new issue