Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ui): add nodes table filter + persistent UI settings #90

Merged
merged 33 commits into from
Dec 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
b7753e3
feat: Port nodes table filter to zwavejs2mqtt
ahochsteger Dec 18, 2020
c9821ad
fix: Lint errors
ahochsteger Dec 18, 2020
5a86567
fix: Lint errors
ahochsteger Dec 18, 2020
f964ea6
refactor: Fix naming: product -> productLabel
ahochsteger Dec 18, 2020
af6537f
fix: Remove unused function productName()
ahochsteger Dec 18, 2020
f2b2bfb
refactor: Use generic header templates
ahochsteger Dec 18, 2020
e5c1a76
fix: Wrong syntax for v-for with templates
ahochsteger Dec 18, 2020
e6d76c2
feat: Add persistent UI settings
ahochsteger Dec 19, 2020
7d4f788
refactor: Major refactoring of column filtering
ahochsteger Dec 27, 2020
19a21ba
chore(deps): bump zwave-js@5.6.1
robertsLando Dec 18, 2020
8244d1e
Release 1.0.0-alpha.1
robertsLando Dec 18, 2020
9fce33d
feat(docker): allow to update devices of driver during build (#86)
varet80 Dec 18, 2020
652b385
fix(hass): better notifications names (#98)
varet80 Dec 21, 2020
81a87c4
fix: startup error in setupLogging() (#96)
ahochsteger Dec 21, 2020
852647d
fix(hass): discovery issues caused by spaces in topic (#99)
robertsLando Dec 21, 2020
2a5cbc7
feat(ui): group values by command classes (#103)
robertsLando Dec 21, 2020
d7d54b1
fix(ui): remove empty layout
robertsLando Dec 22, 2020
5af038c
fix: read only list values in UI and better logging (#102)
robertsLando Dec 22, 2020
eeb5834
fix: writeValue logs undefined valueId
robertsLando Dec 23, 2020
833c027
feat: allow custom ZwaveOptions
robertsLando Dec 23, 2020
94bb377
feat(hass): translate Notification CC values to string (#105)
varet80 Dec 23, 2020
13e6a57
fix: always include endpoint in topic when using named topics #69 (#74)
robertsLando Dec 23, 2020
ec9ed7f
fix: Lint errors
ahochsteger Dec 27, 2020
d1536ff
fix: Lint errors
ahochsteger Dec 27, 2020
f1c2489
Merge branch 'master' into nodes-table-filter
ahochsteger Dec 27, 2020
c3b9d14
fix: Lint errors
ahochsteger Dec 27, 2020
2e060a1
test: Add tests for ColumnFilterHelper
ahochsteger Dec 27, 2020
5fed5f0
fix: Lint errors
ahochsteger Dec 27, 2020
e81770e
fix: Node selection + initSorting (node_id -> id)
ahochsteger Dec 27, 2020
87e19d1
feat: add regex matching for string column filter
ahochsteger Dec 27, 2020
3ecd48c
fix: Error on invalid Regex
ahochsteger Dec 28, 2020
e367cdd
fix: Node selection does not work anymore
ahochsteger Dec 28, 2020
8c9ba2d
Merge branch 'master' into nodes-table-filter
ahochsteger Dec 28, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
import io from 'socket.io-client'
import ConfigApis from '@/apis/ConfigApis'
import Confirm from '@/components/Confirm'
import { Settings } from '@/modules/Settings'

export default {
components: {
Expand Down Expand Up @@ -220,6 +221,7 @@ export default {
{ icon: 'settings', title: 'Settings', path: '/settings' },
{ icon: 'share', title: 'Network graph', path: '/mesh' }
],
settings: new Settings(localStorage),
status: '',
statusColor: '',
drawer: false,
Expand All @@ -228,7 +230,7 @@ export default {
title: '',
snackbar: false,
snackbarText: '',
dark: false,
dark: undefined,
baseURI: ConfigApis.getBasePath()
}
},
Expand All @@ -237,8 +239,7 @@ export default {
this.title = value.name || ''
},
dark (v) {
if (v) localStorage.setItem('dark', 'true')
else localStorage.removeItem('dark')
this.settings.store('dark', this.dark)

this.$vuetify.theme.dark = v
this.changeThemeColor()
Expand Down Expand Up @@ -274,7 +275,7 @@ export default {
this.toggleDrawer()
}

this.dark = !!localStorage.getItem('dark')
this.dark = this.settings.load('dark', false)
this.changeThemeColor()
},
beforeDestroy () {
Expand Down
97 changes: 19 additions & 78 deletions src/components/ControlPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -68,54 +68,11 @@
</v-flex>
</v-layout>

<v-data-table
:headers="headers"
:items="tableNodes"
:footer-props="{
itemsPerPageOptions: [10, 20, { text: 'All', value: -1 }]
}"
:items-per-page.sync="nodeTableItems"
item-key="id"
class="elevation-1"
>
<template v-slot:item="{ item }">
<tr
:style="{
cursor: 'pointer',
background:
selectedNode === item
? $vuetify.theme.themes.light.accent
: 'none'
}"
@click.stop="selectNode(item)"
>
<td>{{ item.id }}</td>
<td>
{{ item.ready ? item.manufacturer : '' }}
</td>
<td>
{{ item.ready ? item.productDescription : '' }}
</td>
<td>
{{ item.ready ? item.productLabel : '' }}
</td>
<td>{{ item.name || '' }}</td>
<td>{{ item.loc || '' }}</td>
<td>{{ item.isSecure ? 'Yes' : 'No' }}</td>
<td>{{ item.isBeaming ? 'Yes' : 'No' }}</td>
<td>{{ item.failed ? 'Yes' : 'No' }}</td>
<td>{{ item.status }}</td>
<td>{{ item.interviewStage }}</td>
<td>
{{
item.lastActive
? new Date(item.lastActive).toLocaleString()
: 'Never'
}}
</td>
</tr>
</template>
</v-data-table>
<nodes-table
:nodes="nodes"
:showHidden="showHidden"
v-on:node-selected="selectNode"
ahochsteger marked this conversation as resolved.
Show resolved Hide resolved
/>

<v-tabs style="margin-top:10px" v-model="currentTab" fixed-tabs>
<v-tab key="node">Node</v-tab>
Expand Down Expand Up @@ -599,6 +556,8 @@ import ValueID from '@/components/ValueId'
import AnsiUp from 'ansi_up'

import DialogSceneValue from '@/components/dialogs/DialogSceneValue'
import NodesTable from '@/components/nodes-table'
import { Settings } from '@/modules/Settings'
import { socketEvents, inboundEvents as socketActions } from '@/plugins/socket'

const ansiUp = new AnsiUp()
Expand All @@ -612,7 +571,8 @@ export default {
},
components: {
ValueID,
DialogSceneValue
DialogSceneValue,
NodesTable
},
computed: {
scenesWithId () {
Expand All @@ -624,9 +584,6 @@ export default {
dialogTitle () {
return this.editedIndex === -1 ? 'New Value' : 'Edit Value'
},
tableNodes () {
return this.showHidden ? this.nodes : this.nodes.filter(n => !n.failed)
},
hassDevices () {
var devices = []
if (this.selectedNode && this.selectedNode.hassDevices) {
Expand Down Expand Up @@ -656,9 +613,6 @@ export default {
}
},
watch: {
nodeTableItems (val) {
localStorage.setItem('nodes_itemsPerPage', val)
},
dialogValue (val) {
val || this.closeDialog()
},
Expand All @@ -668,6 +622,9 @@ export default {
newLoc (val) {
this.locError = this.validateTopic(val)
},
showHidden () {
this.settings.store('nodes_showHidden', this.showHidden)
},
selectedNode () {
if (this.selectedNode) {
this.newName = this.selectedNode.name
Expand Down Expand Up @@ -701,17 +658,17 @@ export default {
},
data () {
return {
settings: new Settings(localStorage),
nodes: [],
scenes: [],
debug: [],
homeid: '',
homeHex: '',
ozwVersion: '',
showHidden: false,
showHidden: undefined,
debugActive: false,
selectedScene: null,
cnt_status: 'Unknown',
nodeTableItems: 10,
newScene: '',
scene_values: [],
dialogValue: false,
Expand Down Expand Up @@ -812,20 +769,6 @@ export default {
locError: null,
newLoc: '',
selectedNode: null,
headers: [
{ text: 'ID', value: 'id' },
{ text: 'Manufacturer', value: 'manufacturer' },
{ text: 'Product', value: 'productDescription' },
{ text: 'Product code', value: 'product' },
{ text: 'Name', value: 'name' },
{ text: 'Location', value: 'loc' },
{ text: 'Secure', value: 'isSecure' },
{ text: 'Beaming', value: 'isBeaming' },
{ text: 'Failed', value: 'failed' },
{ text: 'Status', value: 'status' },
{ text: 'Interview stage', value: 'interviewStage' },
{ text: 'Last Active', value: 'lastActive' }
],
rules: {
required: value => {
var valid = false
Expand All @@ -849,13 +792,13 @@ export default {

return match[0] !== name ? 'Only a-zA-Z0-9_- chars are allowed' : null
},
selectNode (item) {
if (!item) return
selectNode ({ node }) {
if (!node) return

if (this.selectedNode === item) {
if (this.selectedNode === node) {
this.selectedNode = null
} else {
this.selectedNode = this.nodes.find(n => n.id === item.id)
this.selectedNode = this.nodes.find(n => n.id === node.id)
}
},
getValue (v) {
Expand Down Expand Up @@ -1346,9 +1289,7 @@ export default {
mounted () {
var self = this

const itemsPerPage = parseInt(localStorage.getItem('nodes_itemsPerPage'))

this.nodeTableItems = !isNaN(itemsPerPage) ? itemsPerPage : 10
this.showHidden = this.settings.load('nodes_showHidden', false)

this.socket.on(socketEvents.controller, data => {
self.cnt_status = data
Expand Down
139 changes: 139 additions & 0 deletions src/components/nodes-table/ColumnFilter.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<template>
<v-menu :value="show" :close-on-content-click="false" :offset-y="true">
<template v-slot:activator="{ on, attrs }">
<v-icon
small
v-on:click="showOptions"
v-bind="attrs"
v-on="on"
title="Filter options..."
>
{{ hasFilter ? 'filter_list_alt' : 'filter_list' }}
</v-icon>
</template>
<v-card>
<v-icon small v-on:click="hideOptions" right>close</v-icon>
<column-filter-boolean
v-if="column.type == 'boolean'"
:value="value"
@change="change"
></column-filter-boolean>
<column-filter-date
v-if="column.type == 'date'"
:value="value"
@change="change"
></column-filter-date>
<column-filter-number
v-if="column.type == 'number'"
:value="value"
:items="items"
@change="change"
></column-filter-number>
<column-filter-string
v-if="column.type == 'string'"
:value="value"
:items="items"
@change="change"
></column-filter-string>
<v-card-actions>
<v-btn @click="clearFilter">Clear</v-btn>
<v-btn color="primary" @click="confirm" :disabled="!valid">Ok</v-btn>
</v-card-actions>
</v-card>
</v-menu>
</template>

<script>
import ColumnFilterHelper from '@/modules/ColumnFilterHelper'
import ColumnFilterBoolean from './ColumnFilterBoolean'
import ColumnFilterDate from './ColumnFilterDate'
import ColumnFilterNumber from './ColumnFilterNumber'
import ColumnFilterString from './ColumnFilterString'

export default {
components: {
ColumnFilterBoolean,
ColumnFilterDate,
ColumnFilterNumber,
ColumnFilterString
},
props: {
value: {
type: Object,
default: () => {},
required: true
},
column: {
type: Object,
default: () => {},
required: true
},
items: {
type: Array,
default: () => [],
required: true
}
},
data () {
return {
valid: true,
show: false
}
},
computed: {
hasFilter () {
return this.hasDeepValue(this.value)
}
},
methods: {
hasDeepValue (obj) {
return (
obj !== undefined &&
obj !== null &&
Object.keys(obj).some(
k =>
(!!obj[k] && !!Object.keys(obj[k]).length) ||
typeof obj[k] === 'boolean'
)
)
},
showOptions () {
this.show = true
},
hideOptions () {
this.show = false
},
change (value, valid) {
this.valid = valid
if (valid === true) {
// Emit minimal storable filter spec (with empty default values removed):
this.$emit(
'change',
ColumnFilterHelper.filterSpec(this.column.type, value)
)
}
},
confirm () {
this.hideOptions()
},
resetToDefaults () {
// Non-destructive value reset to prevent vue warnings:
const defaults = ColumnFilterHelper.defaultFilter(this.column.type)
Object.assign(this.value, defaults)
for (const key in this.value) {
if (Object.hasOwnProperty.call(this.value, key)) {
Object.keys(this.value).forEach(() => {
if (!Object.keys(defaults).includes(key)) {
delete this.value.key
}
})
}
}
},
clearFilter () {
this.resetToDefaults()
this.change(this.value, true)
}
}
}
</script>
34 changes: 34 additions & 0 deletions src/components/nodes-table/ColumnFilterBoolean.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<template>
<v-card-text>
<v-form>
<v-row>
<v-col>
<v-checkbox
:indeterminate="value.boolValue == null"
v-model="value.boolValue"
label="Boolean value"
@change="change"
></v-checkbox>
</v-col>
</v-row>
</v-form>
</v-card-text>
</template>

<script>
import ColumnFilterHelper from '@/modules/ColumnFilterHelper'
export default {
props: {
value: {
type: Object,
default: () => ColumnFilterHelper.defaultFilter('string'),
required: true
}
},
methods: {
change () {
this.$emit('change', this.value, true)
}
}
}
</script>
Loading