0.1.27 add bubble up 'status' event on every socket
add in latest websocket client examplemaster
parent
1b3d5fceb7
commit
10c4f4a146
|
@ -50,7 +50,6 @@ const socketfuncs = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// client processing function
|
// client processing function
|
||||||
function status(transport) {
|
function status(transport) {
|
||||||
return (packet) => {
|
return (packet) => {
|
||||||
|
|
|
@ -1,28 +1,31 @@
|
||||||
{
|
{
|
||||||
"name": "fio-client",
|
"name": "example-client",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"description": "a websocket client for use with the fiod example",
|
"description": "a websocket client example for use with @uci/websocket-client",
|
||||||
"productName": "Websocket Client",
|
"productName": "Websocket Client",
|
||||||
"cordovaId": "org.cordova.quasar.app",
|
"cordovaId": "org.cordova.quasar.app",
|
||||||
"author": "David Kebler <d@kebler.net>",
|
"author": "David Kebler <d@kebler.net>",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "eslint --ext .js,.vue src",
|
"lint": "eslint --ext .js,.vue src",
|
||||||
"client": "WSS=ws://localhost:8090 ./node_modules/.bin/quasar dev"
|
"client": "./node_modules/.bin/quasar dev"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@quasar/extras": "^1.2.0",
|
"@quasar/extras": "^1.3.1",
|
||||||
"@uci/websocket-client": "^0.1.7",
|
"@uci/websocket-client": "^0.1.8",
|
||||||
"quasar": "^1.0.0",
|
"auto-bind": "^2.1.0",
|
||||||
"rfs": "^8.0.4"
|
"better-try-catch": "^0.6.2",
|
||||||
|
"delay": "^4.3.0",
|
||||||
|
"quasar": "^1.1.0",
|
||||||
|
"rfs": "^9.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@quasar/app": "^1.0.0",
|
"@quasar/app": "^1.0.6",
|
||||||
"@vue/eslint-config-standard": "^4.0.0",
|
"@vue/eslint-config-standard": "^4.0.0",
|
||||||
"babel-eslint": "^10.0.1",
|
"babel-eslint": "^10.0.3",
|
||||||
"eslint": "^5.10.0",
|
"eslint": "^6.3.0",
|
||||||
"eslint-loader": "^2.1.1",
|
"eslint-loader": "^3.0.0",
|
||||||
"eslint-plugin-vue": "^5.0.0"
|
"eslint-plugin-vue": "^5.2.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 8.9.0",
|
"node": ">= 8.9.0",
|
||||||
|
|
|
@ -46,7 +46,9 @@ module.exports = function (ctx) {
|
||||||
'QSelect',
|
'QSelect',
|
||||||
'QField',
|
'QField',
|
||||||
'QFooter',
|
'QFooter',
|
||||||
'QTooltip'
|
'QTooltip',
|
||||||
|
'QBadge',
|
||||||
|
'QScrollArea'
|
||||||
],
|
],
|
||||||
|
|
||||||
directives: [
|
directives: [
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
// import WebSocket from '@uci/websocket-client'
|
|
||||||
import WebSocket from '@uci/websocket-client'
|
import WebSocket from '@uci/websocket-client'
|
||||||
|
|
||||||
const ws = new WebSocket(process.env.WSS || 'ws://0.0.0.0:8090')
|
const ws = new WebSocket(process.env.WSS || 'ws://0.0.0.0:8090')
|
||||||
|
|
|
@ -14,11 +14,7 @@
|
||||||
</q-page-container>
|
</q-page-container>
|
||||||
|
|
||||||
<q-footer elevated class="bg-grey-8 text-white">
|
<q-footer elevated class="bg-grey-8 text-white">
|
||||||
<q-toolbar>
|
|
||||||
<q-toolbar-title>
|
|
||||||
A UCommandIt Library Example
|
|
||||||
</q-toolbar-title>
|
|
||||||
</q-toolbar>
|
|
||||||
</q-footer>
|
</q-footer>
|
||||||
|
|
||||||
</q-layout>
|
</q-layout>
|
||||||
|
@ -28,6 +24,7 @@
|
||||||
export default {
|
export default {
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
|
status: []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<q-page padding :class="{greyedout: offLine}" >
|
<!-- <q-page padding > :class="{greyedout: !connected}" > -->
|
||||||
|
<q-page padding >
|
||||||
<q-list class="">
|
<q-list class="">
|
||||||
<q-item class="">
|
<q-item class="">
|
||||||
<q-item-section side>
|
<q-item-section side>
|
||||||
<q-btn :disabled="offLine" color="secondary" round @click="send" icon="send">
|
<q-btn :disabled="!connected" color="secondary" round @click="send" icon="send">
|
||||||
<q-tooltip>
|
<q-tooltip>
|
||||||
Click to send this command and payload to the sever
|
Click to send this command and payload to the sever
|
||||||
</q-tooltip>
|
</q-tooltip>
|
||||||
|
@ -22,8 +23,7 @@
|
||||||
|
|
||||||
<q-item><div class="col">Property</div>:<div class="col">Value</div></q-item>
|
<q-item><div class="col">Property</div>:<div class="col">Value</div></q-item>
|
||||||
<q-item><q-input class="col" readonly v-model="key[0]" />:<q-input dense class="col" v-model="value[0]" /></q-item>
|
<q-item><q-input class="col" readonly v-model="key[0]" />:<q-input dense class="col" v-model="value[0]" /></q-item>
|
||||||
<q-item><q-input class="col" readonly v-model="key[1]" />:<q-input class="col" v-model="value[1]" /></q-item>
|
<q-item><q-input class="col" label="a custom property in payload" v-model="key[1]" />:<q-input class="col" v-model="value[2]" /></q-item>
|
||||||
<q-item><q-input class="col" label="a custom property in payload" v-model="key[2]" />:<q-input class="col" v-model="value[2]" /></q-item>
|
|
||||||
</q-list>
|
</q-list>
|
||||||
<q-btn :class="switches[0]" @click="toggle(1)" label="Switch 1"><q-tooltip>Click to Toggle Switch</q-tooltip></q-btn>
|
<q-btn :class="switches[0]" @click="toggle(1)" label="Switch 1"><q-tooltip>Click to Toggle Switch</q-tooltip></q-btn>
|
||||||
<q-btn :class="switches[1]" @click="toggle(2)" label="Switch 2"><q-tooltip>Click to Toggle Switch</q-tooltip></q-btn>
|
<q-btn :class="switches[1]" @click="toggle(2)" label="Switch 2"><q-tooltip>Click to Toggle Switch</q-tooltip></q-btn>
|
||||||
|
@ -34,17 +34,63 @@
|
||||||
<q-input v-model="res" readonly label="Socket/Server Response Packet" />
|
<q-input v-model="res" readonly label="Socket/Server Response Packet" />
|
||||||
<q-input v-model="pushed" readonly type="textarea" label="A Packet Pushed from Socket/Server:" />
|
<q-input v-model="pushed" readonly type="textarea" label="A Packet Pushed from Socket/Server:" />
|
||||||
</q-list>
|
</q-list>
|
||||||
|
<div class="row">
|
||||||
|
<q-btn size="40px" class="col q-px-xl q-py-xs" @click.left.exact="connect" @click.shift.left.exact="disconnect" :color="online">
|
||||||
|
<q-tooltip>click to (re)connect, shift click to disconnect</q-tooltip>
|
||||||
|
Socket: {{ connection }}
|
||||||
|
</q-btn>
|
||||||
|
<div class="col">
|
||||||
|
<q-input v-model="initTimeout" readonly label="Initial Connect Timeout" />
|
||||||
|
<q-select
|
||||||
|
v-model="statusLevel"
|
||||||
|
:options="statusOptions"
|
||||||
|
label="Choose Minimum Level to Dislay"
|
||||||
|
emit-value
|
||||||
|
map-options
|
||||||
|
></q-select>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
Status Log: (filter level {{ statusLevel }})
|
||||||
|
<q-scroll-area style="height: 200px">
|
||||||
|
<div v-for="(item, index) in filteredStatusLines" :key="index" class="caption q-py-sm">
|
||||||
|
<q-badge class="shadow-1">
|
||||||
|
{{ item._count }}
|
||||||
|
</q-badge>
|
||||||
|
<q-badge :color="status(item, 'color') || 'brown'" class="shadow-1">
|
||||||
|
{{ status(item,'name') || 'status' }}
|
||||||
|
</q-badge>
|
||||||
|
<q-badge v-if="item.success" color="positive" class="shadow-1">
|
||||||
|
{{ 'success' }}
|
||||||
|
</q-badge>
|
||||||
|
{{ item.msg }}
|
||||||
|
</div>
|
||||||
|
</q-scroll-area>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
</q-page>
|
</q-page>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import btc from 'better-try-catch'
|
import btc from 'better-try-catch'
|
||||||
// import socket from '../socket.js'
|
|
||||||
|
// import {status} from './utils'
|
||||||
|
const statusLevels = {
|
||||||
|
10: { color: 'black', name: 'trace' },
|
||||||
|
20: { color: 'indigo', name: 'debug' },
|
||||||
|
30: { color: 'info', name: 'info' },
|
||||||
|
40: { color: 'warning', name: 'warning' },
|
||||||
|
50: { color: 'red-3', name: 'error' },
|
||||||
|
60: { color: 'negative', name: 'fatal' }
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
cmd: 'switch/toggle',
|
cmd: 'switch/toggle',
|
||||||
offLine: false,
|
connected: false,
|
||||||
commands: [
|
commands: [
|
||||||
{
|
{
|
||||||
label: 'turn a switch on',
|
label: 'turn a switch on',
|
||||||
|
@ -67,91 +113,144 @@ export default {
|
||||||
{
|
{
|
||||||
label: 'ack',
|
label: 'ack',
|
||||||
value: 'ack',
|
value: 'ack',
|
||||||
description: 'turn off something',
|
description: 'send ack packet',
|
||||||
icon: 'no'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'echo',
|
|
||||||
value: 'echo',
|
|
||||||
description: 'echo',
|
|
||||||
icon: 'no'
|
icon: 'no'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
packet: '',
|
packet: '',
|
||||||
res: '',
|
res: '',
|
||||||
key: ['id', 'state', 'sender'],
|
key: ['id', 'sender'],
|
||||||
value: [1, 'off', 'websocket client'],
|
value: [1, 'websocket client'],
|
||||||
pushed: '',
|
pushed: '',
|
||||||
switches: ['off', 'off', 'off', 'off']
|
switches: ['off', 'off', 'off', 'off'],
|
||||||
|
statusLines: [],
|
||||||
|
statusCount: 0,
|
||||||
|
statusLevel: 30,
|
||||||
|
statusOptions: [],
|
||||||
|
name: 'test-client',
|
||||||
|
initTimeout: 30
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
online () {
|
||||||
|
if (this.connected) return 'positive'
|
||||||
|
else return 'warning'
|
||||||
|
},
|
||||||
|
connection () {
|
||||||
|
if (this.connected) return 'Connected'
|
||||||
|
else return 'Offline'
|
||||||
|
},
|
||||||
|
filteredStatusLines () {
|
||||||
|
return this.statusLines.filter(item => { return item.level >= this.statusLevel })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
async connect () {
|
||||||
|
this.statusClear()
|
||||||
|
let [err, res] = await btc(this.$socket.connect)({ initTimeout: this.initTimeout * 1000, name: this.name })
|
||||||
|
if (err) {
|
||||||
|
this.$q.notify({
|
||||||
|
color: 'negative',
|
||||||
|
message: err.msg
|
||||||
|
})
|
||||||
|
this.connected = false
|
||||||
|
} else {
|
||||||
|
this.$q.notify({ color: 'positive', message: res })
|
||||||
|
// this.$socket.listen()
|
||||||
|
this.connected = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async disconnect () {
|
||||||
|
await this.$socket.disconnect()
|
||||||
|
this.clear()
|
||||||
|
this.connected = false
|
||||||
|
},
|
||||||
async send (packet) {
|
async send (packet) {
|
||||||
this.clear()
|
this.clear()
|
||||||
if (!packet.cmd) {
|
if (!packet.cmd) {
|
||||||
packet = {
|
packet = {
|
||||||
cmd: this.cmd,
|
cmd: this.cmd,
|
||||||
[this.key[0]]: this.value[0],
|
[this.key[0]]: this.value[0],
|
||||||
[this.key[1]]: this.value[1],
|
[this.key[1]]: this.value[1]
|
||||||
[this.key[2]]: this.value[2]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.packet = JSON.stringify(packet)
|
this.packet = JSON.stringify(packet)
|
||||||
let [err, res] = await btc(this.$socket.send)(packet)
|
let [err, res] = await btc(this.$socket.send)(packet)
|
||||||
if (err) {
|
if (err) {
|
||||||
console.log('error ', err)
|
|
||||||
this.$q.notify({
|
this.$q.notify({
|
||||||
color: 'negative',
|
color: 'negative',
|
||||||
message: 'Error in repsonse of packet send'
|
message: `Error packet send, ${err}`
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
delete res._header
|
delete res._header
|
||||||
if (res.ack) {
|
|
||||||
this.$q.notify({
|
|
||||||
color: 'positive',
|
|
||||||
message: 'ack reply received'
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
// display on UI
|
||||||
this.res = JSON.stringify(res)
|
this.res = JSON.stringify(res)
|
||||||
}
|
|
||||||
},
|
},
|
||||||
clear () {
|
clear () {
|
||||||
this.pushed = ''
|
this.pushed = ''
|
||||||
this.resMsg = ''
|
this.res = ''
|
||||||
this.resPayload = ''
|
|
||||||
this.packet = ''
|
this.packet = ''
|
||||||
},
|
},
|
||||||
toggle (id) {
|
async toggle (id) {
|
||||||
let packet = { cmd: 'switch/toggle', id: id, state: this.switches[id - 1] }
|
let packet = { cmd: 'switch/toggle', id: id, state: this.switches[id - 1] }
|
||||||
this.send(packet)
|
await this.send(packet)
|
||||||
}
|
|
||||||
},
|
},
|
||||||
async mounted () {
|
status (item, prop) {
|
||||||
this.$q.notify({
|
return statusLevels[item.level] ? statusLevels[item.level][prop] : item[prop] || ''
|
||||||
color: 'info',
|
},
|
||||||
message: `Client connecting to', ${this.$socket.url}`
|
statusClear () {
|
||||||
})
|
this.statusLines = []
|
||||||
let [err] = await btc(this.$socket.connect)()
|
this.statusCount = 0
|
||||||
if (err) {
|
},
|
||||||
this.$q.notify({
|
_statusHandler (packet) {
|
||||||
color: 'negative',
|
// converts level name to number, unknown level converted to 'info' 30
|
||||||
// message: 'Websocket Server Not Available'
|
// console.log(packet.level, packet.msg)
|
||||||
message: err.msg
|
if (typeof packet.level === 'string') {
|
||||||
})
|
packet.level = Number(Object.keys(statusLevels).find(key => { return statusLevels[key].name === packet.level })) || 30
|
||||||
this.offLine = true
|
|
||||||
} else {
|
|
||||||
this.$q.notify({ color: 'positive', message: 'Ready' })
|
|
||||||
this.$socket.listen()
|
|
||||||
this.clear()
|
|
||||||
this.offLine = false
|
|
||||||
}
|
}
|
||||||
this.$socket.on('pushed', packet => {
|
if (packet._clear) this.statusClear()
|
||||||
delete packet._header
|
// if (this.statusLines.length) {
|
||||||
this.pushed = JSON.stringify(packet)
|
// if (packet.level === 10 && this.statusLines[0].level === 10) this.statusLines.shift()
|
||||||
|
// }
|
||||||
|
// if (this.statusLines.length) {
|
||||||
|
// if (packet.level === this.statusLines[0].level && packet.msg === this.statusLines[0].msg) this.statusLines.shift()
|
||||||
|
// }
|
||||||
|
let previous = this.statusLines.findIndex(line => { return (line.level === packet.level && line.msg === packet.msg) })
|
||||||
|
if (previous > -1) this.statusLines.splice(previous, 1)
|
||||||
|
packet._count = ++this.statusCount
|
||||||
|
this.statusLines.unshift(packet)
|
||||||
|
if (this.statusLines > 20) this.statusLines.pop()
|
||||||
|
if (packet.connected != null) this.connected = packet.connected
|
||||||
|
if (packet.success === 'success') this.$q.notify({ color: 'positive', message: packet.msg })
|
||||||
|
},
|
||||||
|
_pushedHandler (packet) {
|
||||||
|
// don't mutate the packet just in case
|
||||||
|
let pushed = Object.assign({}, packet)
|
||||||
|
delete pushed._header
|
||||||
|
this.pushed = JSON.stringify(pushed) // display on UI
|
||||||
if (packet.cmd === 'switch/status') {
|
if (packet.cmd === 'switch/status') {
|
||||||
this.switches[packet.id - 1] = packet.state
|
this.switches[packet.id - 1] = packet.state
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted () {
|
||||||
|
Object.keys(statusLevels).forEach(level => {
|
||||||
|
this.statusOptions.push(
|
||||||
|
{
|
||||||
|
label: statusLevels[level].name,
|
||||||
|
value: Number(level)
|
||||||
|
}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
window.addEventListener('unload', this.disconnect)
|
||||||
|
this.clear()
|
||||||
|
this.$socket.on('status', this._statusHandler.bind(this))
|
||||||
|
this.$socket.on('pushed', this._pushedHandler.bind(this))
|
||||||
|
await this.connect()
|
||||||
|
},
|
||||||
|
beforeDestroy () {
|
||||||
|
window.removeEventListener('unload', this.disconnect)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{
|
{
|
||||||
"ignoreRoot": [".git","examples/ws-fio-client"],
|
"ignoreRoot": [".git","examples/ws-fio-client"],
|
||||||
"watch": ["node_modules/@uci/","node_modules/@uci-utils/","src/","index.js","examples/","test/"]
|
"watch": ["node_modules/@uci/","node_modules/@uci-utils/","src/","index.js","examples/four-in-on.js","examples/four-in-on.js","test/"]
|
||||||
}
|
}
|
||||||
|
|
10
package.json
10
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@uci/base",
|
"name": "@uci/base",
|
||||||
"version": "0.1.26",
|
"version": "0.1.27",
|
||||||
"description": "Multi type and transport JSON packet communication base class. Used in UCI extended classes",
|
"description": "Multi type and transport JSON packet communication base class. Used in UCI extended classes",
|
||||||
"main": "src/base",
|
"main": "src/base",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -30,14 +30,14 @@
|
||||||
"chai": "^4.2.0",
|
"chai": "^4.2.0",
|
||||||
"esm": "^3.2.25",
|
"esm": "^3.2.25",
|
||||||
"mocha": "^6.2.0",
|
"mocha": "^6.2.0",
|
||||||
"nodemon": "^1.19.1"
|
"nodemon": "^1.19.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@uci-utils/bind-funcs": "^0.2.4",
|
"@uci-utils/bind-funcs": "^0.2.4",
|
||||||
"@uci-utils/logger": "^0.0.15",
|
"@uci-utils/logger": "^0.0.15",
|
||||||
"@uci/mqtt": "^0.1.12",
|
"@uci/mqtt": "^0.1.13",
|
||||||
"@uci/socket": "^0.2.19",
|
"@uci/socket": "^0.2.20",
|
||||||
"@uci/websocket": "^0.3.8",
|
"@uci/websocket": "^0.3.9",
|
||||||
"await-to-js": "^2.1.1",
|
"await-to-js": "^2.1.1",
|
||||||
"p-reflect": "^2.1.0",
|
"p-reflect": "^2.1.0",
|
||||||
"p-settle": "^3.1.0"
|
"p-settle": "^3.1.0"
|
||||||
|
|
24
src/base.js
24
src/base.js
|
@ -128,17 +128,6 @@ class Base extends EventEmitter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// console.log('in init', this._socket)
|
|
||||||
// for (let name in this._socket) {
|
|
||||||
// let socket = this._initSocket(name)
|
|
||||||
// console.log(socket)
|
|
||||||
// let [err,res] = await to(socket.init())
|
|
||||||
// if (err) errors[name] = err
|
|
||||||
// results[name] = res
|
|
||||||
// }
|
|
||||||
// this._started = true
|
|
||||||
// return {results:results, errors:errors}
|
|
||||||
|
|
||||||
let sockets = []
|
let sockets = []
|
||||||
for (let name of Object.keys(this._socket)) {
|
for (let name of Object.keys(this._socket)) {
|
||||||
sockets.push(this._initSocket(name))
|
sockets.push(this._initSocket(name))
|
||||||
|
@ -188,17 +177,11 @@ class Base extends EventEmitter {
|
||||||
this._socket[name].transport = transport
|
this._socket[name].transport = transport
|
||||||
this._socket[name]._packetProcess = this._packetProcess.bind(this, name)
|
this._socket[name]._packetProcess = this._packetProcess.bind(this, name)
|
||||||
|
|
||||||
let bubble = (msg) => {
|
// bubble up any socket 'status' events to the base instance
|
||||||
console.log(msg,name,this._socket[name].name)
|
this._socket[name].on('status', ev => {
|
||||||
this._socket[name].on(msg, ev => {
|
|
||||||
ev.socketName=name
|
ev.socketName=name
|
||||||
this.emit(msg, ev)
|
this.emit('status', ev)
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
const msgs = ['error','warn','fatal']
|
|
||||||
msgs.map(bubble) // bubble up any emitted errors
|
|
||||||
|
|
||||||
|
|
||||||
// do this as .then promise then addSocket doesn't need to be async before init
|
// do this as .then promise then addSocket doesn't need to be async before init
|
||||||
if (this._started) return await this._initSocket(name)
|
if (this._started) return await this._initSocket(name)
|
||||||
|
@ -559,7 +542,6 @@ class Base extends EventEmitter {
|
||||||
return 'failed'
|
return 'failed'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
} // end Base Class
|
} // end Base Class
|
||||||
|
|
||||||
export default Base
|
export default Base
|
||||||
|
|
Loading…
Reference in New Issue