From fc2c278c0c61e1aee48f58f2d695dc0ab828a94a Mon Sep 17 00:00:00 2001 From: Amir Reza Ghasemkhani <a.ghasemkhaniii@gmail.com> Date: Fri, 23 Sep 2022 08:06:57 -0700 Subject: [PATCH] feature: add vue tour to the project (#734) --- .../clusters/cluster-filter.spec.js | 7 +- .../clusters/dimmable-light.spec.js | 4 +- package-lock.json | 59 +++ package.json | 1 + src-electron/rest/static-zcl.js | 2 +- src/App.vue | 5 + src/components/ZclAttributeManager.vue | 2 +- .../ZclAttributeReportingManager.vue | 2 +- src/components/ZclClusterManager.vue | 24 +- src/components/ZclClusterView.vue | 18 +- src/components/ZclCommandManager.vue | 2 +- src/components/ZclCreateModifyEndpoint.vue | 78 ++-- src/components/ZclCustomZclView.vue | 7 +- src/components/ZclDomainClusterView.vue | 10 + src/components/ZclEndpointCard.vue | 1 + src/components/ZclEndpointManager.vue | 23 +- src/layouts/ZclConfiguratorLayout.vue | 11 + src/layouts/ZclLayout.vue | 15 +- src/router/routes.js | 2 + src/store/index.js | 4 +- src/store/zap/actions.js | 7 +- src/store/zap/mutations.js | 57 ++- src/store/zap/state.js | 12 +- src/tutorials/VueTour.vue | 351 ++++++++++++++++++ src/tutorials/tutorialConfig.json | 157 ++++++++ 25 files changed, 787 insertions(+), 74 deletions(-) create mode 100644 src/tutorials/VueTour.vue create mode 100644 src/tutorials/tutorialConfig.json diff --git a/cypress/integration/clusters/cluster-filter.spec.js b/cypress/integration/clusters/cluster-filter.spec.js index 7734e89d..79acc63f 100644 --- a/cypress/integration/clusters/cluster-filter.spec.js +++ b/cypress/integration/clusters/cluster-filter.spec.js @@ -22,7 +22,7 @@ describe('Testing cluster filters', () => { cy.get( '[data-test="filter-input"]' ).click() - cy.get('.q-virtual-scroll__content > :nth-child(3)').click() + cy.get('.q-virtual-scroll__content > :nth-child(3)').click({force: true}) cy.fixture('data').then((data) => { cy.get('tbody').children().contains(data.cluster2).should('not.exist') }) @@ -39,9 +39,10 @@ describe('Testing cluster filters', () => { cy.fixture('data').then((data) => { cy.get('tbody').children().should('contain', data.cluster2) }) + cy.get('#General').click({force: true}) cy.get( '#General > .q-expansion-item__container > .q-expansion-item__content > :nth-child(1) > .q-table__container > .q-table__middle > .q-table > tbody > :nth-child(2) > :nth-child(6) > .q-field > .q-field__inner > .q-field__control' - ).click() + ).click({ force: true }) cy.fixture('data').then((data) => { cy.get('.q-virtual-scroll__content > :nth-child(3)') .contains(data.server1) @@ -56,7 +57,7 @@ describe('Testing cluster filters', () => { 'checks if power configuration exists', { retries: { runMode: 2, openMode: 2 } }, () => { - cy.get('.q-virtual-scroll__content > :nth-child(3)').click() + cy.get('.q-virtual-scroll__content > :nth-child(2)').click({force: true}) cy.fixture('data').then((data) => { cy.get('tbody').children().should('contain', data.cluster2) }) diff --git a/cypress/integration/clusters/dimmable-light.spec.js b/cypress/integration/clusters/dimmable-light.spec.js index 2c5540a0..e9a09b5b 100644 --- a/cypress/integration/clusters/dimmable-light.spec.js +++ b/cypress/integration/clusters/dimmable-light.spec.js @@ -34,7 +34,7 @@ describe('Testing Dimmable Light workflow', () => { it('Enabling Client & Server', () => { cy.get( ':nth-child(6) > .q-field > .q-field__inner > .q-field__control' - ).click() + ).click({ force: true }) cy.fixture('data').then((data) => { cy.get('.q-item__section > .q-item__label').contains(data.server2).click() }) @@ -42,7 +42,7 @@ describe('Testing Dimmable Light workflow', () => { it('Check Configuration page', () => { cy.get( ':nth-child(7) > .q-btn > .q-btn__wrapper > .q-btn__content > .notranslate' - ).click() + ).click({ force: true }) cy.fixture('data').then((data) => { cy.get('tr.table_body').contains(data.attribute3).should('be.visible') }) diff --git a/package-lock.json b/package-lock.json index af6d2c81..64aa377e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,6 +38,7 @@ "toposort": "^2.0.2", "utf-8-validate": "^5.0.5", "uuid": "^8.3.2", + "vue-tour": "^2.0.0", "ws": "^8.5.0", "xml2js": "^0.4.23", "xsdlibrary": "^1.3.6", @@ -4882,6 +4883,15 @@ "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==", "dev": true }, + "node_modules/@popperjs/core": { + "version": "2.11.5", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.5.tgz", + "integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@positron/stack-trace": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@positron/stack-trace/-/stack-trace-1.0.0.tgz", @@ -25107,6 +25117,11 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/jump.js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/jump.js/-/jump.js-1.0.2.tgz", + "integrity": "sha512-oUkJJ/Y4ATU5qjkXBntCZSKctbSyS3ewe2jrLaUu/cc9jsQiAn0fnTUxQnZz3mJdDdem1Q279zrD6h3n+Cgxtg==" + }, "node_modules/junk": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz", @@ -37527,6 +37542,22 @@ "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==", "dev": true }, + "node_modules/vue-tour": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vue-tour/-/vue-tour-2.0.0.tgz", + "integrity": "sha512-vhKzqdhunQ3EoO1733UxhOB389u3EKv2X8JqYhX4tIq4ilqlZtnY3azPFBYPFmnAqHn5RyZBrP2CpqSaxTs8og==", + "dependencies": { + "@popperjs/core": "^2.9.1", + "hash-sum": "^2.0.0", + "jump.js": "^1.0.2", + "vue": "^2.6.12" + } + }, + "node_modules/vue-tour/node_modules/hash-sum": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-2.0.0.tgz", + "integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==" + }, "node_modules/vuex": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/vuex/-/vuex-3.6.2.tgz", @@ -43734,6 +43765,11 @@ "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==", "dev": true }, + "@popperjs/core": { + "version": "2.11.5", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.5.tgz", + "integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==" + }, "@positron/stack-trace": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@positron/stack-trace/-/stack-trace-1.0.0.tgz", @@ -59660,6 +59696,11 @@ } } }, + "jump.js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/jump.js/-/jump.js-1.0.2.tgz", + "integrity": "sha512-oUkJJ/Y4ATU5qjkXBntCZSKctbSyS3ewe2jrLaUu/cc9jsQiAn0fnTUxQnZz3mJdDdem1Q279zrD6h3n+Cgxtg==" + }, "junk": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz", @@ -69701,6 +69742,24 @@ "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==", "dev": true }, + "vue-tour": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vue-tour/-/vue-tour-2.0.0.tgz", + "integrity": "sha512-vhKzqdhunQ3EoO1733UxhOB389u3EKv2X8JqYhX4tIq4ilqlZtnY3azPFBYPFmnAqHn5RyZBrP2CpqSaxTs8og==", + "requires": { + "@popperjs/core": "^2.9.1", + "hash-sum": "^2.0.0", + "jump.js": "^1.0.2", + "vue": "^2.6.12" + }, + "dependencies": { + "hash-sum": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-2.0.0.tgz", + "integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==" + } + } + }, "vuex": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/vuex/-/vuex-3.6.2.tgz", diff --git a/package.json b/package.json index 116d10ff..a5426637 100644 --- a/package.json +++ b/package.json @@ -135,6 +135,7 @@ "toposort": "^2.0.2", "utf-8-validate": "^5.0.5", "uuid": "^8.3.2", + "vue-tour": "^2.0.0", "ws": "^8.5.0", "xml2js": "^0.4.23", "xsdlibrary": "^1.3.6", diff --git a/src-electron/rest/static-zcl.js b/src-electron/rest/static-zcl.js index 18d6d492..57e01d69 100644 --- a/src-electron/rest/static-zcl.js +++ b/src-electron/rest/static-zcl.js @@ -59,7 +59,7 @@ async function returnZclEntitiesForClusterId(db, clusterId, packageId) { zclEntityQuery( queryCommand.selectAllCommands, queryCommand.selectCommandsByClusterId - )(db, clusterId).then((z) => + )(db, clusterId, packageId).then((z) => zclEntityQuery( queryEvent.selectAllEvents, queryEvent.selectEventsByClusterId diff --git a/src/App.vue b/src/App.vue index 4f52c0db..aa452c32 100644 --- a/src/App.vue +++ b/src/App.vue @@ -26,11 +26,13 @@ limitations under the License. > <q-icon name="warning" style="font-size: 2.5em; color: red" /> </q-btn> + <VueTour /> </div> </template> <script> import { QSpinnerGears } from 'quasar' +import VueTour from './tutorials/VueTour.vue' const rendApi = require(`../src-shared/rend-api.js`) const restApi = require(`../src-shared/rest-api.js`) const observable = require('./util/observable.js') @@ -75,6 +77,9 @@ function initLoad(store) { export default { name: 'App', + components: { + VueTour, + }, computed: { showExceptionIcon() { return this.$store.state.zap.showExceptionIcon diff --git a/src/components/ZclAttributeManager.vue b/src/components/ZclAttributeManager.vue index fe3c5dfe..c49791ed 100644 --- a/src/components/ZclAttributeManager.vue +++ b/src/components/ZclAttributeManager.vue @@ -61,7 +61,7 @@ limitations under the License. </q-td> <q-td key="included" :props="props" auto-width> <q-toggle - class="q-mt-xs" + class="q-mt-xs v-step-11" v-model="selection" :val="hashAttributeIdClusterId(props.row.id, selectedCluster.id)" indeterminate-value="false" diff --git a/src/components/ZclAttributeReportingManager.vue b/src/components/ZclAttributeReportingManager.vue index 4793df3e..9c29fc54 100644 --- a/src/components/ZclAttributeReportingManager.vue +++ b/src/components/ZclAttributeReportingManager.vue @@ -60,7 +60,7 @@ limitations under the License. <q-td key="enabled" :props="props" auto-width> <q-toggle :disable="checkReportingPolicy(props.row)" - class="q-mt-xs" + class="q-mt-xs v-step-13" v-model="selectedReporting" :val="hashAttributeIdClusterId(props.row.id, selectedCluster.id)" indeterminate-value="false" diff --git a/src/components/ZclClusterManager.vue b/src/components/ZclClusterManager.vue index d1bd850a..5b75500f 100644 --- a/src/components/ZclClusterManager.vue +++ b/src/components/ZclClusterManager.vue @@ -21,8 +21,10 @@ limitations under the License. <div class="row"> <q-toolbar> <q-toolbar-title style="font-weight: bolder"> - Endpoint - {{ this.endpointId[this.selectedEndpointId] }} Clusters + <span class="v-step-6" + >Endpoint + {{ this.endpointId[this.selectedEndpointId] }} Clusters</span + > </q-toolbar-title> </q-toolbar> </div> @@ -39,7 +41,7 @@ limitations under the License. </div> - <div> + <div class="v-step-7"> <q-select outlined :value="filter" @@ -81,12 +83,13 @@ limitations under the License. </q-input> </div> <q-list style="padding-bottom: 250px"> - <div v-for="domainName in domainNames" :key="domainName.id"> + <div v-for="(domainName, index) in domainNames" :key="domainName.id"> <div v-show="clusterDomains(domainName).length > 0"> <q-expansion-item :id="domainName" switch-toggle-side :label="domainName" + :ref="domainName + index" @input="setOpenDomain(domainName, $event)" :value="getDomainOpenState(domainName)" data-test="Cluster" @@ -124,6 +127,9 @@ export default { enabledClusters() { this.changeDomainFilter(this.filter) }, + expanded() { + this.$refs[this.$store.state.zap.domains[0] + 0][0].show() + }, }, computed: { domainNames: { @@ -184,6 +190,16 @@ export default { return this.$store.state.zap.clusterManager.actionOptions }, }, + isTutorialRunning: { + get() { + return this.$store.state.zap.isTutorialRunning + }, + }, + expanded: { + get() { + return this.$store.state.zap.expanded + }, + }, }, methods: { scrollToElementById(tag) { diff --git a/src/components/ZclClusterView.vue b/src/components/ZclClusterView.vue index 07c75c6c..b1fee0d7 100644 --- a/src/components/ZclClusterView.vue +++ b/src/components/ZclClusterView.vue @@ -72,8 +72,12 @@ limitations under the License. </div> <div class="q-pb-sm"> <q-tabs v-model="tab" dense active-color="blue" align="left"> - <q-tab name="attributes" label="Attributes" /> - <q-tab name="reporting" label="Attribute Reporting" /> + <q-tab name="attributes" label="Attributes" class="v-step-10" /> + <q-tab + name="reporting" + label="Attribute Reporting" + class="v-step-12" + /> <q-tab name="commands" label="Commands" /> <q-tab name="events" label="Events" v-show="events.length > 0" /> </q-tabs> @@ -138,6 +142,16 @@ export default { return this.$store.state.zap.events }, }, + tutorialTab: { + get() { + return this.$store.state.zap.showReportTabInCluster + }, + }, + }, + watch: { + tutorialTab(val) { + this.tab = val + }, }, methods: { setIndividualClusterFilterString(filterString) { diff --git a/src/components/ZclCommandManager.vue b/src/components/ZclCommandManager.vue index 5a36465e..e61a73ba 100644 --- a/src/components/ZclCommandManager.vue +++ b/src/components/ZclCommandManager.vue @@ -75,7 +75,7 @@ limitations under the License. " /> </q-td> - <q-td key="in" :props="props" auto-width> + <q-td key="in" :props="props" auto-width class="v-step-14"> <q-checkbox class="q-mt-xs" v-model="selectionIn" diff --git a/src/components/ZclCreateModifyEndpoint.vue b/src/components/ZclCreateModifyEndpoint.vue index 92a07075..198619f5 100644 --- a/src/components/ZclCreateModifyEndpoint.vue +++ b/src/components/ZclCreateModifyEndpoint.vue @@ -15,7 +15,7 @@ limitations under the License. --> <template> <div> - <q-card> + <q-card class="v-step-1"> <q-card-section> <div class="text-h6 text-align:left"> {{ this.endpointReference ? 'Edit Endpoint' : 'Create New Endpoint' }} @@ -27,7 +27,7 @@ limitations under the License. v-model="shownEndpoint.endpointIdentifier" ref="endpoint" filled - class="col" + class="col v-step-3" :rules="[reqInteger, reqPosInt, reqUniqueEndpoint]" min="0" /> @@ -47,18 +47,22 @@ limitations under the License. ref="device" outlined filled - class="col" + class="col v-step-2" use-input hide-selected fill-input :options="deviceTypeOptions" - v-model="computedDeviceTypeRefAndDeviceIdPair" + v-model="deviceTypeRefAndDeviceIdPair" :rules="[(val) => val != null || '* Required']" :option-label="getDeviceOptionLabel" @filter="filterDeviceTypes" @input="setDeviceTypeCallback" - data-test="select-endpoint-input" - /> + data-test="select-endpoint-input" + > + <template v-slot:selected> + <template> Choose an option </template> + </template> + </q-select> <div class="q-gutter-md row"> <q-input label="Network" @@ -92,11 +96,16 @@ limitations under the License. </q-form> </q-card-section> <q-card-actions> - <q-btn label="Cancel" v-close-popup class="col" /> + <q-btn + label="Cancel" + @click="toggleCreateEndpointModal" + v-close-popup + class="col" + /> <q-btn :label="endpointReference ? 'Save' : 'Create'" color="primary" - class="col" + class="col v-step-4" @click="saveOrCreateHandler()" /> </q-card-actions> @@ -115,6 +124,11 @@ export default { props: ['endpointReference'], emits: ['saveOrCreateValidated'], mixins: [CommonMixin], + watch: { + deviceTypeRefAndDeviceIdPair(val) { + this.setDeviceTypeCallback(val) + }, + }, mounted() { if (this.endpointReference != null) { this.shownEndpoint.endpointIdentifier = parseInt( @@ -130,7 +144,7 @@ export default { this.shownEndpoint.deviceVersion = parseInt( this.endpointVersion[this.endpointReference] ) - this.shownEndpoint.deviceTypeRefAndDeviceIdPair = { + this.deviceTypeRefAndDeviceIdPair = { deviceTypeRef: this.endpointDeviceTypeRef[this.endpointType[this.endpointReference]], deviceIdentifier: this.endpointDeviceId[this.endpointReference], @@ -147,10 +161,6 @@ export default { profileIdentifier: null, networkIdentifier: 0, deviceVersion: 1, - deviceTypeRefAndDeviceIdPair: { - deviceTypeRef: null, - deviceIdentifier: null, - }, }, saveOrCreateCloseFlag: false, } @@ -206,9 +216,9 @@ export default { return this.$store.state.zap.endpointView.deviceId }, }, - computedDeviceTypeRefAndDeviceIdPair: { + deviceTypeRefAndDeviceIdPair: { get() { - return this.shownEndpoint.deviceTypeRefAndDeviceIdPair + return this.$store.state.zap.deviceTypeRefAndDeviceIdPair }, }, computedProfileId: { @@ -234,6 +244,10 @@ export default { }, }, methods: { + // This function will close the endpoint modal + toggleCreateEndpointModal() { + this.$store.commit('zap/toggleEndpointModal', false) + }, setProfileId(value) { this.shownEndpoint.profileIdentifier = value }, @@ -251,9 +265,8 @@ export default { profileId = this.asHex(this.zclDeviceTypes[deviceTypeRef].profileId, 4) } this.shownEndpoint.profileIdentifier = profileId - this.shownEndpoint.deviceTypeRefAndDeviceIdPair.deviceTypeRef = - value.deviceTypeRef - this.shownEndpoint.deviceTypeRefAndDeviceIdPair.deviceIdentifier = + this.deviceTypeRefAndDeviceIdPair.deviceTypeRef = value.deviceTypeRef + this.deviceTypeRefAndDeviceIdPair.deviceIdentifier = value.deviceIdentifier }, saveOrCreateHandler() { @@ -271,7 +284,7 @@ export default { this.editEpt(this.shownEndpoint, this.endpointReference) this.$emit('updateData') } else { - this.newEpt(this.shownEndpoint) + this.newEpt() } } }, @@ -292,12 +305,11 @@ export default { 'Endpoint identifier must be unique' ) }, - newEpt(shownEndpoint) { + newEpt() { this.$store .dispatch(`zap/addEndpointType`, { name: 'Anonymous Endpoint Type', - deviceTypeRef: - shownEndpoint.deviceTypeRefAndDeviceIdPair.deviceTypeRef, + deviceTypeRef: this.deviceTypeRefAndDeviceIdPair.deviceTypeRef, }) .then((response) => { this.$store @@ -308,8 +320,7 @@ export default { endpointType: response.id, endpointVersion: this.shownEndpoint.deviceVersion, deviceIdentifier: - this.shownEndpoint.deviceTypeRefAndDeviceIdPair - .deviceIdentifier, + this.deviceTypeRefAndDeviceIdPair.deviceIdentifier, }) .then((res) => { if (this.shareClusterStatesAcrossEndpoints()) { @@ -342,6 +353,7 @@ export default { }) this.$store.dispatch('zap/updateSelectedEndpoint', res.id) + this.$store.commit('zap/toggleEndpointModal', false) }) }) }, @@ -351,7 +363,7 @@ export default { this.$store.dispatch('zap/updateEndpointType', { endpointTypeId: endpointTypeReference, updatedKey: RestApi.updateKey.deviceTypeRef, - updatedValue: shownEndpoint.deviceTypeRefAndDeviceIdPair.deviceTypeRef, + updatedValue: this.deviceTypeRefAndDeviceIdPair.deviceTypeRef, }) this.$store.dispatch('zap/updateEndpoint', { @@ -375,9 +387,7 @@ export default { }, { updatedKey: RestApi.updateKey.deviceId, - value: parseInt( - shownEndpoint.deviceTypeRefAndDeviceIdPair.deviceIdentifier - ), + value: parseInt(this.deviceTypeRefAndDeviceIdPair.deviceIdentifier), }, ], }) @@ -425,9 +435,8 @@ export default { try { done( { - deviceTypeRef: this.shownEndpoint.deviceTypeRefAndDeviceIdPair - .deviceTypeRef - ? this.shownEndpoint.deviceTypeRefAndDeviceIdPair.deviceTypeRef + deviceTypeRef: this.deviceTypeRefAndDeviceIdPair.deviceTypeRef + ? this.deviceTypeRefAndDeviceIdPair.deviceTypeRef : this.customDeviceIdReference, deviceIdentifier: parseInt(val), }, @@ -455,5 +464,12 @@ export default { }) }, }, + destroyed() { + // This function will empty the deviceTypeRef state + this.$store.commit('zap/setDeviceTypeRefAndDeviceIdPair', { + deviceTypeRef: null, + deviceIdentifier: null, + }) + }, } </script> diff --git a/src/components/ZclCustomZclView.vue b/src/components/ZclCustomZclView.vue index a2339b64..b0832c40 100644 --- a/src/components/ZclCustomZclView.vue +++ b/src/components/ZclCustomZclView.vue @@ -27,14 +27,15 @@ limitations under the License. <q-btn color="primary" icon="add" + class="v-step-16" @click="browseForFile()" /> </div> </q-card-section> <q-card-section> <q-list bordered separator> - <template v-for="(sessionPackage, index) in packages"> - <q-item v-bind:key="index"> + <div v-for="(sessionPackage, index) in packages" :key="index"> + <q-item> <q-item-section> <q-expansion-item> <template slot="header"> @@ -71,7 +72,7 @@ limitations under the License. </q-expansion-item> </q-item-section> </q-item> - </template> + </div> </q-list> </q-card-section> </q-card> diff --git a/src/components/ZclDomainClusterView.vue b/src/components/ZclDomainClusterView.vue index 8956ac5a..96cc15ef 100644 --- a/src/components/ZclDomainClusterView.vue +++ b/src/components/ZclDomainClusterView.vue @@ -132,6 +132,7 @@ limitations under the License. </q-td> <q-td key="enable" :props="props"> <q-select + class="v-step-8" :v-model="getClusterEnabledStatus(props.row.id)" :value="getClusterEnabledStatus(props.row.id)" :display-value="`${getClusterEnabledStatus(props.row.id)}`" @@ -144,6 +145,7 @@ limitations under the License. <q-td key="configure" :props="props"> <q-btn flat + class="v-step-9" :color="isClusterEnabled(props.row.id) ? 'primary' : 'grey'" dense :disable="!isClusterEnabled(props.row.id)" @@ -454,6 +456,14 @@ export default { ], } }, + created() { + // This function check you created endpoint before and right now you are in the tutorial steps, then sets cluster data + if (this.clusters !== undefined) { + if(this.clusters[0].domainName == this.$store.state.zap.domains[0]) { + this.$store.commit('zap/setClusterDataForTutorial', this.clusters[0]) + } + } + }, } </script> diff --git a/src/components/ZclEndpointCard.vue b/src/components/ZclEndpointCard.vue index f3da86c8..c6b2bd8a 100644 --- a/src/components/ZclEndpointCard.vue +++ b/src/components/ZclEndpointCard.vue @@ -17,6 +17,7 @@ limitations under the License. <template> <div> <q-card + class="v-step-5" :bordered="isSelectedEndpoint" @click="setSelectedEndpointType(endpointReference)" > diff --git a/src/components/ZclEndpointManager.vue b/src/components/ZclEndpointManager.vue index 04d0bf02..799c047b 100644 --- a/src/components/ZclEndpointManager.vue +++ b/src/components/ZclEndpointManager.vue @@ -19,9 +19,9 @@ limitations under the License. <!-- Add onClick handler for new endpoint--> <div class="row"> <q-btn - class="vertical-align:middle q-pa-md q-mini-drawer-hide row-8" + class="vertical-align:middle q-pa-md q-mini-drawer-hide row-8 v-step-0" text-color="primary" - @click="newEndpointDialog = true" + @click="toggleCreateEndpointModal()" icon="add" label="Add New Endpoint" flat @@ -42,19 +42,18 @@ limitations under the License. /> </div> <q-separator class="q-mini-drawer-hide" /> - <template v-for="(child, index) in endpoints"> <zcl-endpoint-card + v-for="(child, index) in endpoints" v-bind:key="index" v-bind:endpointReference="child.id" class="q-mini-drawer-hide" > </zcl-endpoint-card> - </template> - <q-dialog v-model="newEndpointDialog" class="background-color:transparent"> + <q-dialog v-model="showEndpointModal" class="background-color:transparent"> <zcl-create-modify-endpoint v-bind:endpointReference="null" - v-on:saveOrCreateValidated="newEndpointDialog = false" + v-on:saveOrCreateValidated="toggleCreateEndpointModal()" /> </q-dialog> </div> @@ -101,11 +100,23 @@ export default { })) }, }, + // This computed will show create endpoint modal, its trigger with vue tour + showEndpointModal: { + get() { + return this.$store.state.zap.showCreateModifyEndpoint + }, + }, }, data() { return { newEndpointDialog: false, } }, + methods: { + // This function changing the modal state + toggleCreateEndpointModal() { + this.$store.commit('zap/toggleEndpointModal', true) + }, + }, } </script> diff --git a/src/layouts/ZclConfiguratorLayout.vue b/src/layouts/ZclConfiguratorLayout.vue index bfbb5f5d..ba7c1af4 100644 --- a/src/layouts/ZclConfiguratorLayout.vue +++ b/src/layouts/ZclConfiguratorLayout.vue @@ -66,6 +66,7 @@ limitations under the License. icon="list" align="center" flat + class="v-step-15" :ripple="false" :unelevated="false" :outline="false" @@ -226,6 +227,16 @@ export default { ) }, }, + zclExtensionDialogInTutorial: { + get() { + return this.$store.state.zap.openZclExtensionsDialog + }, + }, + }, + watch: { + zclExtensionDialogInTutorial(val) { + this.zclExtensionDialog = val + }, }, data() { return { diff --git a/src/layouts/ZclLayout.vue b/src/layouts/ZclLayout.vue index 51ba743f..d8c6635e 100644 --- a/src/layouts/ZclLayout.vue +++ b/src/layouts/ZclLayout.vue @@ -56,13 +56,16 @@ limitations under the License. v-on:click="getGeneratedFiles" data-test="preview" /> + <q-btn flat icon="settings" id="preference" to="/preference"> + <q-tooltip> Preferences </q-tooltip> + </q-btn> <q-btn + v-if="this.$store.state.zap.showDevTools" flat - icon="settings" - id="preference" - to="/preference" + @click="showTutorial" + icon="psychology_alt" > - <q-tooltip> Preferences </q-tooltip> + <q-tooltip> Tutorial </q-tooltip> </q-btn> <q-btn flat @click="homeDialog = !homeDialog" icon="mdi-alert-circle"> <q-tooltip> About </q-tooltip> @@ -189,6 +192,10 @@ const observable = require('../util/observable.js') export default { name: 'ZclLayout', methods: { + // This function will start vue tour steps + showTutorial() { + this.$tours['ZclTour'].start() + }, togglePreviewTab() { this.$store.commit('zap/togglePreviewTab') }, diff --git a/src/router/routes.js b/src/router/routes.js index 795956ce..b4c81250 100644 --- a/src/router/routes.js +++ b/src/router/routes.js @@ -18,6 +18,7 @@ const routes = [ { path: '/', + name: 'Home', component: () => import('layouts/ZclLayout.vue'), children: [ { path: '', component: () => import('layouts/ZclLayout.vue') }, // Consider making this a "New Project" page @@ -25,6 +26,7 @@ const routes = [ }, { path: '/cluster', + name: 'cluster', component: () => import('components/ZclClusterView.vue'), }, { diff --git a/src/store/index.js b/src/store/index.js index 2b0c8133..933841ff 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -22,11 +22,13 @@ */ import Vue from 'vue' import Vuex from 'vuex' +import VueTour from 'vue-tour' +require('vue-tour/dist/vue-tour.css') // we first import the module import zap from './zap' -Vue.use(Vuex) +Vue.use(VueTour).use(Vuex) export default function (/* { ssrContext } */) { const Store = new Vuex.Store({ diff --git a/src/store/zap/actions.js b/src/store/zap/actions.js index 1467609d..e6bfa950 100644 --- a/src/store/zap/actions.js +++ b/src/store/zap/actions.js @@ -799,11 +799,12 @@ export function generateAllEndpointsData(context, endpointData) { enabledClients.push(record.clusterRef) } else { enabledServers.push(record.clusterRef) + + } } - } + }) + server = [...enabledServers, ...enabledClients] }) - server = [...enabledServers, ...enabledServers] - }) let promise2 = Vue.prototype.$serverGet(endpointData.attributesRequestUrl).then((res) => { diff --git a/src/store/zap/mutations.js b/src/store/zap/mutations.js index 4f566e40..74f192c5 100644 --- a/src/store/zap/mutations.js +++ b/src/store/zap/mutations.js @@ -353,10 +353,6 @@ export function setAttributeLists(state, data) { ) } -export function setEventLists(state, selected) { - Vue.set(state.eventView, 'selectedEvents', selected) -} - export function setCommandLists(state, data) { Vue.set(state.commandView, 'selectedIn', data.incoming) Vue.set(state.commandView, 'selectedOut', data.outgoing) @@ -537,16 +533,57 @@ export function setAllEndpointsData(state, value) { id: value.endpointId }) } -export function updateIsProfileIdShown (state, value) { - value == 0 ? state.isProfileIdShown = false : state.isProfileIdShown = true +// This function change state of showCreateModifyEndpoint and will show or hide create endpoint modal +export function toggleEndpointModal(state, value) { + state.showCreateModifyEndpoint = value } -// This function will update the cluster stage if cluster changed it will update the endpoint data -export function updateIsClusterOptionChanged(state, value) { - state.isClusterOptionChanged = value +// This function will show you is tutorial step running or not +export function toggleTutorial(state, value) { + state.isTutorialRunning = value +} + +// This function will expand the cluster so you can see data in it ( this function used for vue tour ) +export function triggerExpanded(state, value) { + state.expanded = value +} + +// This function will change the tab of the cluster configuration page +export function openReportTabInCluster(state, value) { + state.showReportTabInCluster = value +} + +// This function will open the extension modal ( this function used for vue tour ) +export function openZclExtensionsDialogForTutorial(state, value) { + state.openZclExtensionsDialog = value +} + +// This function will set data of the endpoint that you created for showing clusters +export function setClusterDataForTutorial(state, value) { + state.clusterDataForTutorial = value +} + +// This function will check whether should we show the profile id to the users or no +export function updateIsProfileIdShown(state, value) { + value == 0 + ? (state.isProfileIdShown = false) + : (state.isProfileIdShown = true) +} + +// This function will sets the deviceTypeRef and deviceIdentifier so users can see which device chosen in the tutorial +export function setDeviceTypeRefAndDeviceIdPair(state, value) { + state.deviceTypeRefAndDeviceIdPair = { + deviceTypeRef: value.deviceTypeRef, + deviceIdentifier: value.deviceIdentifier, + } } // This function will toggle showEndpointData state and save that state export function toggleShowEndpoint(state, item) { Vue.set(state.showEndpointData, item.id, item.value) -} \ No newline at end of file +} + +// This function will update the cluster stage if cluster changed it will update the endpoint data +export function updateIsClusterOptionChanged(state, value) { + state.isClusterOptionChanged = value +} diff --git a/src/store/zap/state.js b/src/store/zap/state.js index 88ce8997..35e7e971 100644 --- a/src/store/zap/state.js +++ b/src/store/zap/state.js @@ -19,6 +19,12 @@ const restApi = require('../../../src-shared/rest-api.js') export default function () { return { isProfileIdShown: null, + clusterDataForTutorial: [], + isTutorialRunning: false, + openZclExtensionsDialog: false, + showReportTabInCluster: '', + expanded: false, + showCreateModifyEndpoint: false, showPreviewTab: false, isExceptionsExpanded: false, exceptions: [], @@ -146,6 +152,10 @@ export default function () { }, allEndpointsData: {}, isClusterOptionChanged: false, - showEndpointData: {} + showEndpointData: {}, + deviceTypeRefAndDeviceIdPair: { + deviceTypeRef: null, + deviceIdentifier: null, + }, } } diff --git a/src/tutorials/VueTour.vue b/src/tutorials/VueTour.vue new file mode 100644 index 00000000..c499787d --- /dev/null +++ b/src/tutorials/VueTour.vue @@ -0,0 +1,351 @@ +<!-- +Copyright (c) 2008,2020 Silicon Labs. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +--> +<template> + <div> + <v-tour + name="ZclTour" + :steps="tutorialSteps" + :callbacks="{ onFinish: disableTutorial, onSkip: disableTutorial }" + ></v-tour> + <q-dialog + v-model="deletingTutorialEndpoint" + class="background-color:transparent" + > + <q-card> + <q-card-section> + <div class="text-h6">Delete Endpoint</div> + + Do you want to delete the endpoint used for the tutorial? + </q-card-section> + <q-card-actions> + <q-btn label="Cancel" v-close-popup class="col" /> + <q-btn + :label="'Delete'" + color="primary" + class="col" + v-close-popup="deleteEndpointDialog" + @click="deleteEndpoint()" + id="delete_last_endpoint" + /> + </q-card-actions> + </q-card> + </q-dialog> + </div> +</template> +<script> +import tutorialConfig from './tutorialConfig.json' +import CommonMixin from '../util/common-mixin' +export default { + name: 'VueTour', + computed: { + endpoints: { + get() { + return Array.from(this.endpointIdListSorted.keys()).map((id) => ({ + id: id, + })) + }, + }, + tourCluster: { + get() { + return this.$store.state.zap.clusterDataForTutorial + }, + }, + zclDeviceTypeOptions: { + get() { + let dt = this.$store.state.zap.zclDeviceTypes + let keys = Object.keys(dt).sort((a, b) => { + return dt[a].description.localeCompare(dt[b].description) + }) + return keys.map((item) => { + return { deviceTypeRef: item, deviceIdentifier: dt[item].code } + }) + }, + }, + }, + methods: { + // This function will create a endpoint for tutorial + createNewEndpointForTour(resolve) { + if (this.endpoints.length < 1) { + this.$store + .dispatch(`zap/addEndpointType`, { + name: 'Anonymous Endpoint Type', + deviceTypeRef: this.zclDeviceTypeOptions[0].deviceTypeRef, + }) + .then((response) => { + let profileId = this.asHex(this.zclDeviceTypes[this.zclDeviceTypeOptions[0].deviceTypeRef].profileId, 4) + this.$store + .dispatch(`zap/addEndpoint`, { + endpointId: parseInt(this.getSmallestUnusedEndpointId()), + networkId: 0, + profileId: parseInt(profileId), + endpointType: response.id, + endpointVersion: 1, + deviceIdentifier: this.zclDeviceTypeOptions[0].deviceIdentifier, + }) + .then((res) => { + if (this.shareClusterStatesAcrossEndpoints()) { + this.$store.dispatch( + 'zap/shareClusterStatesAcrossEndpoints', + { + endpointTypeIdList: this.endpointTypeIdList, + } + ) + } + this.$store.dispatch('zap/updateSelectedEndpointType', { + endpointType: this.endpointType[res.id], + deviceTypeRef: + this.endpointDeviceTypeRef[this.endpointType[res.id]], + }) + // collect all cluster id from new endpoint + this.selectionClients.forEach((id) => { + this.updateSelectedComponentRequest({ + clusterId: id, + side: ['client'], + added: true, + }) + }) + this.selectionServers.forEach((id) => { + this.updateSelectedComponentRequest({ + clusterId: id, + side: ['server'], + added: true, + }) + }) + this.$store.dispatch('zap/updateSelectedEndpoint', res.id) + resolve() + }) + }) + } else { + this.$store.dispatch('zap/updateSelectedEndpointType', { + endpointType: this.endpointType[this.endpoints[0].id], + deviceTypeRef: + this.endpointDeviceTypeRef[this.endpointType[this.endpoints[0].id]], + }) + this.$store.dispatch('zap/updateSelectedEndpoint', this.endpoints[0].id) + resolve() + } + }, + // This function will show the delete modal to the user + handleDeletionDialog() { + this.deletingTutorialEndpoint = true + this.deleteEndpointDialog = !this.deleteEndpointDialog + }, + // ---------- Vue Tour Functions ---------- // + // This function will delete the endpoint that tutorial created for you + deleteEndpoint() { + this.$store + .dispatch('zap/deleteEndpoint', this.endpoints[0].id) + .then(() => { + this.$store.dispatch( + 'zap/deleteEndpointType', + this.endpointType[this.endpoints[0].id] + ) + }) + this.deletingTutorialEndpoint = false + this.deleteEndpointDialog = false + this.$store.commit('zap/openZclExtensionsDialogForTutorial', false) + }, + // This function will disable tutorial + disableTutorial() { + if (this.$router.currentRoute.path === '/') { + this.$store.commit('zap/toggleTutorial', false) + this.$store.commit('zap/triggerExpanded', false) + this.endpoints.length > 0 ? this.handleDeletionDialog() : '' + this.$store.commit('zap/toggleEndpointModal', false) + } else { + this.$router + .push({ name: 'Home' }) + .then(() => {}) + .then(() => { + this.endpoints.length > 0 ? this.handleDeletionDialog() : '' + }) + } + }, + // This function will set isTutorialRunning to true + startTutorialAndCloseTheEndpointModal() { + return new Promise((resolve) => { + this.$store.commit('zap/toggleEndpointModal', false) + this.$store.commit('zap/toggleTutorial', true) + resolve() + }) + }, + // This function will open the create endpoint modal + openEndpointModal() { + return new Promise((resolve) => { + this.$store.commit('zap/toggleEndpointModal', true) + setTimeout(() => { + resolve() + }, 250) + }) + }, + // This function will set Device inpute in the create endpoint modal + setDeviceEndpoint() { + return new Promise((resolve) => { + this.$store.commit('zap/setDeviceTypeRefAndDeviceIdPair', { + deviceTypeRef: this.zclDeviceTypeOptions[0].deviceTypeRef, + deviceIdentifier: this.zclDeviceTypeOptions[0].deviceIdentifier, + }) + resolve() + }) + }, + // This function will create a mock endpoint for tutorial + createMockEndpoint() { + return new Promise((resolve) => { + this.$store.commit('zap/toggleEndpointModal', true) + setTimeout(() => { + resolve() + }, 300) + }) + }, + // This function will create a endpoint card for that mock endpoint + generateEndpointCard() { + return new Promise((resolve) => { + this.createNewEndpointForTour(resolve) + this.$store.commit('zap/toggleEndpointModal', false) + }) + }, + // This function will expand first cluster and show the next step of vue tour to the user + expandCluster() { + return new Promise((resolve) => { + this.$store.commit('zap/triggerExpanded', true) + resolve() + }) + }, + // This function will move user to the home page + comeBackToHomePage() { + return new Promise((resolve) => { + if (this.$router.currentRoute.path !== '/') { + this.$router.push({ name: 'Home' }).then(() => { + this.$store.commit('zap/triggerExpanded', true) + resolve() + }) + } else { + resolve() + } + }) + }, + // This function will open the cluster configuration page + openConfigure() { + return new Promise((resolve) => { + if (this.$router.currentRoute.path === '/') { + this.$store.commit('zap/triggerExpanded', false) + this.$store + .dispatch('zap/updateSelectedCluster', this.tourCluster) + .then(() => { + this.$store.dispatch( + 'zap/refreshEndpointTypeCluster', + this.selectedEndpointTypeId + ) + this.$store.dispatch('zap/setLastSelectedDomain', this.$store.state.zap.domains[0]) + }) + this.$router.push({ name: 'cluster' }).then(() => { + resolve() + }) + } else { + resolve() + } + }) + }, + // This function will change the tab of configuration page to attribute + backToAttributesTab() { + return new Promise((resolve) => { + this.$store.commit('zap/openReportTabInCluster', 'attributes') + resolve() + }) + }, + // This function will change the tab of configuration page to report + openReportTabInCluster() { + return new Promise((resolve) => { + this.$store.commit('zap/openReportTabInCluster', 'reporting') + resolve() + }) + }, + // This function will change the tab of configuration page to commands + openCommandsTabInCluster() { + return new Promise((resolve) => { + if (this.$router.currentRoute.path === '/') { + this.$router.push({ name: 'cluster' }).then(() => { + setTimeout(() => { + this.$store.commit('zap/openReportTabInCluster', 'commands') + resolve() + }, 200) + }) + } else { + this.$store.commit('zap/openReportTabInCluster', 'commands') + setTimeout(() => { + resolve() + }, 200) + } + }) + }, + // This function will move user to the home page + backToHomePage() { + return new Promise((resolve) => { + if (this.$router.currentRoute.path !== '/') { + this.$store.commit('zap/openReportTabInCluster', 'attributes') + this.$router.push({ name: 'Home' }).then(() => { + resolve() + }) + } else { + this.$store.commit('zap/openZclExtensionsDialogForTutorial', false) + resolve() + } + }) + }, + // This function will open extension modal + openZclExtensionDialog() { + return new Promise((resolve) => { + this.$store.commit('zap/openZclExtensionsDialogForTutorial', true) + setTimeout(() => { + resolve() + }, 300) + }) + }, + }, + mixins: [CommonMixin], + data() { + return { + tutorialSteps: [], + deletingTutorialEndpoint: false, + deleteEndpointDialog: false, + } + }, + mounted() { + if (this.$store.state.zap.showDevTools) { + this.$tours['ZclTour'].start() + } + }, + created() { + let config = [] + tutorialConfig.tutorialSteps.forEach((item) => { + config.push({ + target: item.target, + header: { + title: item.title, + }, + params: { + placement: item.placement, + enableScrolling: item.enableScrolling, + highlight: item.highlight, + }, + content: item.content, + before: () => (item.before !== '' ? this[item.before]() : ''), + }) + }) + this.tutorialSteps = config + }, +} +</script> diff --git a/src/tutorials/tutorialConfig.json b/src/tutorials/tutorialConfig.json new file mode 100644 index 00000000..b21cf3f3 --- /dev/null +++ b/src/tutorials/tutorialConfig.json @@ -0,0 +1,157 @@ +{ + "tutorialSteps": [ + { + "target": ".v-step-0", + "title": "Adding Endpoints", + "content": "A Zigbee application can have multiple endpoints. Each endpoint contains a device configuration made up of Clusters on that endpoint. Add a new endpoint to your application by clicking <strong>ADD NEW ENDPOINT</strong> in the top left corner of the Zigbee Cluster Configurator interface", + "before": "startTutorialAndCloseTheEndpointModal", + "placement": "bottom", + "enableScrolling": true, + "highlight": true + }, + { + "target": ".v-step-1", + "title": "", + "content": "A dialog opens in which you can select the device type for the endpoint.", + "before": "openEndpointModal", + "placement": "top", + "enableScrolling": true, + "highlight": true + }, + { + "target": ".v-step-2", + "title": "", + "content": "From here you can select whether you would like the endpoint to represent something like a Light or a Door Lock. You can find the Zigbee device type by entering the name of the device in the <strong>Device</strong> field", + "before": "", + "placement": "right", + "enableScrolling": true, + "highlight": true + }, + { + "target": ".v-step-3", + "title": "", + "content": "To change the number of the endpoint on which you would like this device to appear, change the <strong>Endpoint</strong> setting", + "before": "setDeviceEndpoint", + "placement": "left", + "enableScrolling": true, + "highlight": true + }, + { + "target": ".v-step-4", + "title": "", + "content": "Once you have configured the endpoint, click <strong>CREATE</strong> to add the endpoint to your configuration.", + "before": "createMockEndpoint", + "placement": "right", + "enableScrolling": true, + "highlight": true + }, + { + "target": ".v-step-5", + "title": "Modifying an Endpoint", + "content": "Select an endpoint to modify by clicking on the endpoint configuration on the left side of the Zigbee Cluster Configurator. The Endpoint highlighted with a blue border is the endpoint that you are in the process of modifying.", + "before": "generateEndpointCard", + "placement": "right", + "enableScrolling": true, + "highlight": true + }, + { + "target": ".v-step-6", + "title": "", + "content": "When the endpoint is highlighted, you can modify the clusters enabled on that endpoint in the cluster configuration editor on the right side of the Zigbee Cluster Configurator.", + "before": "", + "placement": "bottom", + "enableScrolling": false, + "highlight": true + }, + { + "target": ".v-step-7", + "title": "", + "content": "The Show dropdown gives you options by which to view the clusters that are available or enabled on your endpoint. To see only clusters that are enabled on the endpoint, select the ‘Enabled Clusters’ option.", + "before": "", + "placement": "right", + "enableScrolling": false, + "highlight": true + }, + { + "target": ".v-step-8", + "title": "Configuring a Cluster", + "content": "You can enable either the Client or Server (or both) sides of a cluster, by changing the ‘Client’ and ‘Server’ settings in the Enable column. Note that, depending on the changes you make, you may be notified that components have been added to your project.", + "before": "expandCluster", + "placement": "top", + "enableScrolling": false, + "highlight": true + }, + { + "target": ".v-step-9", + "title": "Configuring a Cluster", + "content": "Each cluster can be configured to implement Zigbee cluster attributes and commands. The Enable Command Discovery toggle in the top interface ribbon allows the list of commands supported by the device to be visible through the Zigbee General Command Discovery command interface.", + "before": "comeBackToHomePage", + "placement": "right", + "enableScrolling": false, + "highlight": true + }, + { + "target": ".v-step-10", + "title": "Configuring Attributes", + "content": "Attributes are configured through the first tab in the cluster configuration interface, as shown.", + "before": "openConfigure", + "placement": "top", + "enableScrolling": false, + "highlight": true + }, + { + "target": ".v-step-11", + "title": "", + "content": "To enable or disable an attribute for a given cluster, click the On/Off toggle. When the toggle is shaded blue and to the right the attribute is ON. If the toggle is grey and to the left, the attribute is off and thus not enabled for that cluster. In the figure below, the on/off attribute is implemented, whereas the reporting status attribute is not.", + "before": "", + "placement": "bottom", + "enableScrolling": false, + "highlight": true + }, + { + "target": ".v-step-12", + "title": "Configuring Attribute Reporting", + "content": "Default attribute reporting is configured through the Attribute Reporting tab in the cluster configuration interface.", + "before": "backToAttributesTab", + "placement": "bottom", + "enableScrolling": false, + "highlight": true + }, + { + "target": ".v-step-13", + "title": "", + "content": "As with attribute enablement, default attribute reporting is controlled through the toggle interface on the left of the Attribute Reporting table. An attribute is set up to be reported by default when the toggle is on.", + "before": "openReportTabInCluster", + "placement": "bottom", + "enableScrolling": false, + "highlight": true + }, + { + "target": ".v-step-14", + "title": "", + "content": "For instance, in the following example, Endpoint 1 only implements the Server side of the On/Off cluster. Therefore, it is only possible for the cluster to receive the Off command ‘In’, which is in fact enabled for that cluster. With this setting enabled, the Zigbee Cluster Configurator automatically generates command handling code for handling the On/Off cluster’s ‘Off’ command. This will ensure that, when an ‘Off’ command is received, it will be routed to the correct command handling callback", + "before": "openCommandsTabInCluster", + "placement": "bottom", + "enableScrolling": false, + "highlight": true + }, + { + "target": ".v-step-15", + "title": "Adding Custom Clusters", + "content": "In Zigbee, developers can add their own custom clusters to their applications. This functionality is supported by the Zigbee Cluster Library (ZCL). The custom ZCL must be described in the Silicon Labs ZCL XML format. For examples of custom ZCL XML, see the file sampleextensions.xml in the <GSDK install location>/app/zcl directory. This XML file can be used as a reference for your custom ZCL XML file. Once you have created your custom ZCL XML file, it can be added to your project through the ZCL EXTENSIONS… interface", + "before": "backToHomePage", + "placement": "right", + "enableScrolling": false, + "highlight": true + }, + { + "target": ".v-step-16", + "title": "", + "content": "Clicking ZCL EXTENSIONS… opens the Custom ZCL page in the Zigbee Cluster Configurator. Click Add to add your custom ZCL XML to the project. Browse to the location of the custom ZCL XML, select a file, and click Open. Once the custom ZCL XML has been read into the Zigbee Cluster Configurator, your custom clusters, attributes, commands, and so on\nare accessible to the configuration of your application.", + "before": "openZclExtensionDialog", + "placement": "bottom", + "enableScrolling": false, + "highlight": true + } + ] +} -- GitLab