summaryrefslogtreecommitdiffstats
path: root/webapp/src/components/DataTable.vue
diff options
context:
space:
mode:
Diffstat (limited to 'webapp/src/components/DataTable.vue')
-rw-r--r--webapp/src/components/DataTable.vue123
1 files changed, 119 insertions, 4 deletions
diff --git a/webapp/src/components/DataTable.vue b/webapp/src/components/DataTable.vue
index a537af3..69656ed 100644
--- a/webapp/src/components/DataTable.vue
+++ b/webapp/src/components/DataTable.vue
@@ -8,7 +8,12 @@
"regex": "Regex",
"caseSensitive": "Case sensitive",
"onlyShowSelected": "Selected entries only",
- "selected": "selected"
+ "selected": "selected",
+ "copyDone": "Copied to Clipboard",
+ "nothingToCopy": "Nothing to Copy",
+ "copyHeading": "Copy to Clipboard",
+ "copy": "Copy",
+ "close": "Close"
},
"de": {
"all": "Alle",
@@ -18,7 +23,12 @@
"regex": "Regex",
"caseSensitive": "Groß-/Kleinschreibung beachten",
"onlyShowSelected": "Nur ausgewählte Einträg",
- "selected": "ausgewählt"
+ "selected": "ausgewählt",
+ "copyDone": "In die Zwischenablage kopiert",
+ "nothingToCopy": "Nichts zu kopieren",
+ "copyHeading": "In die Zwischenablage kopieren",
+ "copy": "Kopieren",
+ "close": "Schließen"
}
}
</i18n>
@@ -32,7 +42,7 @@
@filter="filteredRows = $event"
:selected="value"
:items="sortedRows"
- :data-keys="headers.filter(h => h.text !== undefined)"
+ :data-keys="headersWithText"
nested-data
:regex="regex"
:case-sensitive="caseSensitive"
@@ -133,6 +143,9 @@
arrow_upward
</v-icon>
</div>
+ <div v-if="exportButton" class="copy-button">
+ <v-btn icon @click="copyDialog = !copyDialog"><v-icon style="opacity: 0.3;">file_copy</v-icon></v-btn>
+ </div>
</div>
<div class="header-separator" style="height: 2px">
<loading-bar :loading="loading" :transparent="false"></loading-bar>
@@ -158,6 +171,42 @@
<div v-if="filteredRows.length === 0" class="no-result">{{ $t('noResult') }}</div>
</template>
</RecycleScroller>
+ <v-dialog
+ v-model="copyDialog"
+ scrollable
+ :max-width="300"
+ :fullscreen="$vuetify.breakpoint.smAndDown"
+ lazy
+ >
+ <v-card class="non-selectable">
+ <v-card-title class="elevation-3">
+ <div class="subheading">{{ $t('copyHeading') }}</div>
+ <v-spacer></v-spacer>
+ <v-btn-toggle v-model="copyFormat" mandatory class="copy-format-toggle">
+ <v-btn small :color="copyFormat === 0 ? 'primary' : ''">CSV</v-btn>
+ <v-btn small :color="copyFormat === 1 ? 'primary' : ''">JSON</v-btn>
+ </v-btn-toggle>
+ </v-card-title>
+ <v-card-text style="height: 100%">
+ <textarea ref="copyHelper" style="display: none"></textarea>
+ <v-checkbox
+ v-for="header in headersWithText"
+ v-model="header.includeInCopy"
+ :key="header.key"
+ color="primary"
+ :label="header.text"
+ hide-details
+ class="copy-header-checkbox"
+ ></v-checkbox>
+ </v-card-text>
+ <v-divider></v-divider>
+ <v-card-actions>
+ <v-spacer></v-spacer>
+ <v-btn small flat @click="copyDialog = false">{{ $t('close') }}</v-btn>
+ <v-btn small color="primary" @click="copyToClipboard">{{ $t('copy') }}</v-btn>
+ </v-card-actions>
+ </v-card>
+ </v-dialog>
</div>
</template>
@@ -208,6 +257,10 @@ export default {
noSort: {
type: Boolean,
default: false
+ },
+ exportButton: {
+ type: Boolean,
+ default: false
}
},
data () {
@@ -221,7 +274,9 @@ export default {
onlyShowSelected: false,
lastSelectIndex: null,
shiftKey: false,
- filteredRows: []
+ filteredRows: [],
+ copyDialog: false,
+ copyFormat: 0
}
},
computed: {
@@ -254,6 +309,9 @@ export default {
selectedIconMap () {
if (this.singleSelect) return { 'true': 'radio_button_checked', 'false': 'radio_button_unchecked' }
return { 'true': 'check_box', 'false': 'check_box_outline_blank' }
+ },
+ headersWithText () {
+ return this.headers.filter(h => h.text !== undefined)
}
},
watch: {
@@ -394,6 +452,43 @@ export default {
})
if (this.onlyShowSelected && this.$refs.search) this.$refs.search.filterRows()
if (this.filteredRows) this.calcSelectState()
+ },
+ copyToClipboard () {
+ let result = ''
+ const keys = this.headersWithText.filter(h => h.includeInCopy).map(x => x.key)
+ if (this.copyFormat === 0) {
+ const topIndex = keys.length - 1
+ this.filteredRows.forEach(row => {
+ let rowString = ''
+ for (let i in keys) {
+ let value = row.data[keys[i]]
+ if (value) rowString += value
+ if (i < topIndex) rowString += ','
+ }
+ if (rowString) result += rowString + '\n'
+ })
+ } else if (this.copyFormat === 1) {
+ const rows = this.filteredRows.map(row => {
+ let filterdRow = {}
+ for (let i in keys) {
+ let k = keys[i]
+ filterdRow[k] = row.data[k]
+ }
+ return filterdRow
+ })
+ result = JSON.stringify(rows, null, 4)
+ }
+ if (!result) {
+ this.$snackbar({ text: this.$t('nothingToCopy'), color: 'error', timeout: 1200 })
+ return
+ }
+ const copyHelper = this.$refs.copyHelper
+ copyHelper.style.display = 'block'
+ copyHelper.value = result
+ copyHelper.select()
+ document.execCommand('copy')
+ copyHelper.style.display = 'none'
+ this.$snackbar({ text: this.$t('copyDone'), color: 'primary', timeout: 1200 })
}
}
}
@@ -405,6 +500,25 @@ export default {
white-space: nowrap;
}
+.copy-button {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ right: 8px;
+ margin: 0 !important;
+ display: flex;
+ align-items: center;
+}
+
+.copy-header-checkbox {
+ margin: 0;
+ padding: 0;
+}
+
+.copy-format-toggle .v-btn--active {
+ color: white;
+}
+
.toggle-button {
margin: 0;
padding: 0;
@@ -466,6 +580,7 @@ export default {
}
.table-head {
+ position: relative;
height: 56px;
display: flex;
font-size: 12px;