vue frontend

This commit is contained in:
Tropicananass 2025-05-12 18:02:33 +02:00
parent 4b3b2d1772
commit 017b2e5a8c
25 changed files with 1099 additions and 490 deletions

View File

@ -0,0 +1,5 @@
{
"include": [
"./src/**/*"
]
}

View File

@ -8,9 +8,12 @@
"name": "webapp",
"version": "0.1.0",
"dependencies": {
"@jaames/iro": "^5.5.2",
"bootstrap": "^5.1.2",
"core-js": "^3.6.5",
"vue": "^3.0.0"
"vue": "^3.0.0",
"vue-native-websocket-vue3": "^3.1.4",
"vuex": "^4.0.2"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
@ -1669,6 +1672,20 @@
"webpack": "^4.0.0"
}
},
"node_modules/@irojs/iro-core": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@irojs/iro-core/-/iro-core-1.2.1.tgz",
"integrity": "sha512-p2OvsBSSmidsDsTSkID6jEyXDF7lcyxPrkh3qBzasBZFpjkYd6kZ3yMWai3MlAaQ3F7li/Et7rSJVV09Fpei+A=="
},
"node_modules/@jaames/iro": {
"version": "5.5.2",
"resolved": "https://registry.npmjs.org/@jaames/iro/-/iro-5.5.2.tgz",
"integrity": "sha512-Fbi5U4Vdkw6UsF+R3oMlPONqkvUDMkwzh+mX718gQsDFt3+1r1jvGsrfCbedmXAAy0WsjDHOrefK0BkDk99TQg==",
"dependencies": {
"@irojs/iro-core": "^1.2.1",
"preact": "^10.0.0"
}
},
"node_modules/@mrmlnc/readdir-enhanced": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz",
@ -2495,6 +2512,11 @@
"integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
"dev": true
},
"node_modules/@vue/devtools-api": {
"version": "6.0.0-beta.19",
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.0.0-beta.19.tgz",
"integrity": "sha512-ObzQhgkoVeoyKv+e8+tB/jQBL2smtk/NmC9OmFK8UqdDpoOdv/Kf9pyDWL+IFyM7qLD2C75rszJujvGSPSpGlw=="
},
"node_modules/@vue/preload-webpack-plugin": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@vue/preload-webpack-plugin/-/preload-webpack-plugin-1.1.2.tgz",
@ -3269,8 +3291,7 @@
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"node_modules/base": {
"version": "0.11.2",
@ -3480,7 +3501,6 @@
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -4508,8 +4528,7 @@
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"node_modules/concat-stream": {
"version": "1.6.2",
@ -7031,8 +7050,7 @@
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
},
"node_modules/fsevents": {
"version": "2.3.2",
@ -7142,7 +7160,6 @@
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
"integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
"dev": true,
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
@ -7959,7 +7976,6 @@
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"dependencies": {
"once": "^1.3.0",
"wrappy": "1"
@ -7968,8 +7984,7 @@
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"node_modules/inquirer": {
"version": "7.3.3",
@ -9328,7 +9343,6 @@
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"dependencies": {
"brace-expansion": "^1.1.7"
},
@ -9941,7 +9955,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"dependencies": {
"wrappy": "1"
}
@ -10319,7 +10332,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
@ -11155,6 +11167,15 @@
"node": ">=0.10.0"
}
},
"node_modules/preact": {
"version": "10.5.15",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.5.15.tgz",
"integrity": "sha512-5chK29n6QcJc3m1lVrKQSQ+V7K1Gb8HeQY6FViQ5AxCAEGu3DaHffWNDkC9+miZgsLvbvU9rxbV1qinGHMHzqA==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/preact"
}
},
"node_modules/prelude-ls": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
@ -14199,6 +14220,60 @@
"integrity": "sha1-M7QHd3VMZDJXPBIMw4CLvRDUfwQ=",
"dev": true
},
"node_modules/vue-native-websocket-vue3": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/vue-native-websocket-vue3/-/vue-native-websocket-vue3-3.1.4.tgz",
"integrity": "sha512-Dqq+LaoV1ZgWhPGIEBKBIXMc+n+7Bh6BkAgnRrf3iV6NSOA7q42dQNOEPbJCzmn/L779ck56RT1UtoCZfIwq/w==",
"dependencies": {
"file-entry-cache": "^6.0.1"
},
"peerDependencies": {
"core-js": "^3.6.5",
"vue": "^3.0.0"
}
},
"node_modules/vue-native-websocket-vue3/node_modules/file-entry-cache": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
"integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
"dependencies": {
"flat-cache": "^3.0.4"
},
"engines": {
"node": "^10.12.0 || >=12.0.0"
}
},
"node_modules/vue-native-websocket-vue3/node_modules/flat-cache": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
"integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
"dependencies": {
"flatted": "^3.1.0",
"rimraf": "^3.0.2"
},
"engines": {
"node": "^10.12.0 || >=12.0.0"
}
},
"node_modules/vue-native-websocket-vue3/node_modules/flatted": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz",
"integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA=="
},
"node_modules/vue-native-websocket-vue3/node_modules/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"dependencies": {
"glob": "^7.1.3"
},
"bin": {
"rimraf": "bin.js"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/vue-style-loader": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz",
@ -14221,6 +14296,17 @@
"integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==",
"dev": true
},
"node_modules/vuex": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/vuex/-/vuex-4.0.2.tgz",
"integrity": "sha512-M6r8uxELjZIK8kTKDGgZTYX/ahzblnzC4isU1tpmEuOIIKmV+TRdc+H4s8ds2NuZ7wpUTdGRzJRtoj+lI+pc0Q==",
"dependencies": {
"@vue/devtools-api": "^6.0.0-beta.11"
},
"peerDependencies": {
"vue": "^3.0.2"
}
},
"node_modules/watchpack": {
"version": "1.7.5",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz",
@ -15134,8 +15220,7 @@
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
},
"node_modules/write": {
"version": "1.0.3",
@ -16550,6 +16635,20 @@
"postcss": "^7.0.0"
}
},
"@irojs/iro-core": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@irojs/iro-core/-/iro-core-1.2.1.tgz",
"integrity": "sha512-p2OvsBSSmidsDsTSkID6jEyXDF7lcyxPrkh3qBzasBZFpjkYd6kZ3yMWai3MlAaQ3F7li/Et7rSJVV09Fpei+A=="
},
"@jaames/iro": {
"version": "5.5.2",
"resolved": "https://registry.npmjs.org/@jaames/iro/-/iro-5.5.2.tgz",
"integrity": "sha512-Fbi5U4Vdkw6UsF+R3oMlPONqkvUDMkwzh+mX718gQsDFt3+1r1jvGsrfCbedmXAAy0WsjDHOrefK0BkDk99TQg==",
"requires": {
"@irojs/iro-core": "^1.2.1",
"preact": "^10.0.0"
}
},
"@mrmlnc/readdir-enhanced": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz",
@ -17251,6 +17350,11 @@
}
}
},
"@vue/devtools-api": {
"version": "6.0.0-beta.19",
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.0.0-beta.19.tgz",
"integrity": "sha512-ObzQhgkoVeoyKv+e8+tB/jQBL2smtk/NmC9OmFK8UqdDpoOdv/Kf9pyDWL+IFyM7qLD2C75rszJujvGSPSpGlw=="
},
"@vue/preload-webpack-plugin": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@vue/preload-webpack-plugin/-/preload-webpack-plugin-1.1.2.tgz",
@ -17891,8 +17995,7 @@
"balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"base": {
"version": "0.11.2",
@ -18067,7 +18170,6 @@
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -18912,8 +19014,7 @@
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"concat-stream": {
"version": "1.6.2",
@ -20921,8 +21022,7 @@
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
},
"fsevents": {
"version": "2.3.2",
@ -21004,7 +21104,6 @@
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
"integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
@ -21632,7 +21731,6 @@
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"requires": {
"once": "^1.3.0",
"wrappy": "1"
@ -21641,8 +21739,7 @@
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"inquirer": {
"version": "7.3.3",
@ -22701,7 +22798,6 @@
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -23198,7 +23294,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"requires": {
"wrappy": "1"
}
@ -23504,8 +23599,7 @@
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
},
"path-is-inside": {
"version": "1.0.2",
@ -24223,6 +24317,11 @@
"integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==",
"dev": true
},
"preact": {
"version": "10.5.15",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.5.15.tgz",
"integrity": "sha512-5chK29n6QcJc3m1lVrKQSQ+V7K1Gb8HeQY6FViQ5AxCAEGu3DaHffWNDkC9+miZgsLvbvU9rxbV1qinGHMHzqA=="
},
"prelude-ls": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
@ -26741,6 +26840,46 @@
}
}
},
"vue-native-websocket-vue3": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/vue-native-websocket-vue3/-/vue-native-websocket-vue3-3.1.4.tgz",
"integrity": "sha512-Dqq+LaoV1ZgWhPGIEBKBIXMc+n+7Bh6BkAgnRrf3iV6NSOA7q42dQNOEPbJCzmn/L779ck56RT1UtoCZfIwq/w==",
"requires": {
"file-entry-cache": "^6.0.1"
},
"dependencies": {
"file-entry-cache": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
"integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
"requires": {
"flat-cache": "^3.0.4"
}
},
"flat-cache": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
"integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
"requires": {
"flatted": "^3.1.0",
"rimraf": "^3.0.2"
}
},
"flatted": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz",
"integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA=="
},
"rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"requires": {
"glob": "^7.1.3"
}
}
}
},
"vue-style-loader": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz",
@ -26765,6 +26904,14 @@
"integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==",
"dev": true
},
"vuex": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/vuex/-/vuex-4.0.2.tgz",
"integrity": "sha512-M6r8uxELjZIK8kTKDGgZTYX/ahzblnzC4isU1tpmEuOIIKmV+TRdc+H4s8ds2NuZ7wpUTdGRzJRtoj+lI+pc0Q==",
"requires": {
"@vue/devtools-api": "^6.0.0-beta.11"
}
},
"watchpack": {
"version": "1.7.5",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz",
@ -27494,8 +27641,7 @@
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
},
"write": {
"version": "1.0.3",

View File

@ -8,9 +8,12 @@
"lint": "vue-cli-service lint"
},
"dependencies": {
"@jaames/iro": "^5.5.2",
"bootstrap": "^5.1.2",
"core-js": "^3.6.5",
"vue": "^3.0.0"
"vue": "^3.0.0",
"vue-native-websocket-vue3": "^3.1.4",
"vuex": "^4.0.2"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",

View File

@ -1,17 +1,23 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>
<%= htmlWebpackPlugin.options.title %>
</title>
</head>
<body class="bg-dark text-white">
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

View File

@ -1,295 +1,85 @@
<template>
<div class="bg-dark text-white">
<div class="container">
<div class="row py-4 text-center" id="header">
<h1>Tropicananass Leds</h1>
</div>
<div class="container">
<div class="row py-4 text-center" id="header">
<h1>Tropicananass Leds</h1>
</div>
<WSConnection v-if="!this.isConnected"></WSConnection>
<Reconnect></Reconnect>
<ModeSelect mode_list="{Test, Artnet, Auto, Manual}"></ModeSelect>
<div class="row pt-2 gy-2">
<div class="col d-flex justify-content-center">
<form
id="mode-input"
class="btn-group"
role="group"
aria-label="Select mode"
>
<input
type="radio"
class="btn-check"
name="mode-selection"
id="test-mode"
autocomplete="off"
value="0"
checked
/>
<input
type="radio"
class="btn-check"
name="mode-selection"
id="artnet-mode"
autocomplete="off"
value="1"
/>
<input
type="radio"
class="btn-check"
name="mode-selection"
id="auto-mode"
autocomplete="off"
value="2"
/>
<input
type="radio"
class="btn-check"
name="mode-selection"
id="manual-mode"
autocomplete="off"
value="3"
/>
</form>
</div>
<div class="col d-flex justify-content-center">
<form
id="pattern-input"
class="btn-group"
role="group"
aria-label="Select pattern"
>
<input
type="radio"
class="btn-check"
name="pattern-selection"
id="pulse-pattern"
autocomplete="off"
value="0"
checked
/>
<input
type="radio"
class="btn-check"
name="pattern-selection"
id="travel-pattern"
autocomplete="off"
value="1"
/>
<input
type="radio"
class="btn-check"
name="pattern-selection"
id="strobe-pattern"
autocomplete="off"
value="2"
/>
</form>
</div>
</div>
<div class="row pt-2 gy-2">
<legend class="col-12 col-sm-auto">Channel</legend>
<!-- <div class="col d-flex justify-content-center">
<form id="channelGlobal-input" class="btn-group" role="group" aria-label="Select channel">
<input type="radio" class="btn-check" name="channelGlobal-selection" id="all-channel" autocomplete="off" checked>
<input type="radio" class="btn-check" name="channelGlobal-selection" id="none-channel" autocomplete="off">
</form>
</div> -->
<div class="col d-flex justify-content-center">
<form
id="channel-input"
class="btn-group"
role="group"
aria-label="Select channel"
>
<!-- todo: Change to checkbox -->
<input
type="radio"
class="btn-check"
name="channel-selection"
id="all-channel"
autocomplete="off"
checked
/>
<input
type="radio"
class="btn-check"
name="channel-selection"
id="0-channel"
autocomplete="off"
checked
/>
<input
type="radio"
class="btn-check"
name="channel-selection"
id="1-channel"
autocomplete="off"
checked
/>
<input
type="radio"
class="btn-check"
name="channel-selection"
id="2-channel"
autocomplete="off"
checked
/>
<input
type="radio"
class="btn-check"
name="channel-selection"
id="3-channel"
autocomplete="off"
checked
/>
<input
type="radio"
class="btn-check"
name="channel-selection"
id="4-channel"
autocomplete="off"
checked
/>
<input
type="radio"
class="btn-check"
name="channel-selection"
id="5-channel"
autocomplete="off"
checked
/>
<input
type="radio"
class="btn-check"
name="channel-selection"
id="6-channel"
autocomplete="off"
checked
/>
<input
type="radio"
class="btn-check"
name="channel-selection"
id="7-channel"
autocomplete="off"
checked
/>
</form>
</div>
</div>
<div class="row pt-2 gy-2">
<label
class="form-label col-9 col-sm-2 text-sm-end"
for="formControlRange"
>Sensitivity</label
>
<span class="col-3 col-sm-1 text-end">50</span>
<div class="col col-sm-9">
<input
type="range"
class="form-range"
id="formControlRange"
oninput="rangeCallback(this)"
/>
</div>
</div>
<div class="row pt-2 gy-2">
<label
class="form-label col-9 col-sm-2 text-sm-end"
for="formControlRange"
>Gravity</label
>
<span class="col-3 col-sm-1 text-end">50</span>
<div class="col col-sm-9">
<input
type="range"
class="form-range col-auto"
id="formControlRange"
oninput="rangeCallback(this)"
/>
</div>
</div>
<div class="row pt-2 gy-2">
<div class="col">
<div class="form-check form-switch">
<input
class="form-check-input"
type="checkbox"
id="modulateColor"
onclick="colorModulateCallback(this)"
/>
<label class="form-check-label" for="modulateColor"
>Modulate color</label
>
</div>
</div>
</div>
<div class="row pt-2 gy-2">
<div id="mainPicker" class="col d-flex justify-content-center"></div>
<div
id="modulationPicker"
class="col d-none d-flex justify-content-center"
></div>
</div>
<div class="row pt-2 gy-2">
<label
class="form-label col-9 col-sm-2 text-sm-end"
for="formControlRange"
>Modulation speed</label
>
<span class="col-3 col-sm-auto text-end" id="rangeval">50</span>
<div class="col col-sm-9">
<input
type="range"
class="form-range col-auto"
id="formControlRange"
oninput="rangeCallback(this)"
/>
</div>
</div>
<div class="row pt-2 gy-2">
<label
class="form-label col-9 col-sm-2 text-sm-end"
for="formControlRange"
>Freq</label
>
<span class="col-3 col-sm-auto text-end" id="rangeval">50</span>
<div class="col col-sm-9">
<input
type="range"
class="form-range col-auto"
id="formControlRange"
oninput="rangeCallback(this)"
/>
</div>
</div>
<div class="row pt-2 gy-2 justify-content-evenly align-items-center">
<RadioButtonGroup
group_name="mode"
:label_list="['test', 'artnet', 'auto', 'manual']"
:checked_index="mode"
:callback="modeSelectCallback"
></RadioButtonGroup>
<AutoMode v-if="mode == 2"></AutoMode>
</div>
</div>
<h1>Hello !!</h1>
<img alt="Vue logo" src="./assets/logo.png" />
<HelloWorld msg="Welcome to Your Vue.js App" />
</template>
<script>
import Reconnect from "./components/Reconnect.vue";
import ModeSelect from "./components/ModeSelect.vue";
import RadioButtonGroup from "./components/ui/RadioButtonGroup.vue";
import AutoMode from "./components/AutoMode.vue";
import WSConnection from "./components/WSConnection.vue";
const data = {
mode: 2,
ws: null,
readyState: 0,
};
export default {
name: "App",
data() {
return data;
},
components: {
Reconnect,
ModeSelect,
RadioButtonGroup,
AutoMode,
WSConnection,
},
created() {
this.ws = new WebSocket("ws://192.168.4.1:8080");
this.ws.onopen = function (event) {
console.log(`[open] ws: Connection established: ${event.code}`);
console.log(this);
};
this.ws.onmessage = function (event) {
console.log(`[message] ws: Data received from server: ${event.data}`);
};
this.ws.onclose = function (event) {
if (event.wasClean) {
console.log(
`[close] ws: Connection closed cleanly, code=${event.code} reason=${event.reason}`
);
} else {
console.log("[close] ws: Connection died");
}
};
this.ws.onerror = function (error) {
console.log(`[error] ws: ${error.message}`);
};
},
computed: {
isConnected() {
return this.readyState == WebSocket.OPEN;
},
},
methods: {
modeSelectCallback: function (event) {
if (event.target.nodeName == "INPUT") {
this.mode = Number(event.target.value);
console.log("m:" + this.mode);
}
},
},
};
</script>
<style></style>
<style scoped>
.row {
margin: 0;
padding: 0;
}
</style>

View File

@ -0,0 +1,67 @@
<template>
<RadioButtonGroup
group_name="pattern"
:label_list="['pulse', 'travel', 'strobe']"
:checked_index="pattern"
:callback="patternSelectCallback"
></RadioButtonGroup>
<div class="w-100 d-block d-lg-none"></div>
<RadioButtonGroup
group_name="channel"
:label_list="['0', '1', '2', '3', '4', '5', '6', '7', 'all']"
:checked_index="channel"
:callback="channelSelectCallback"
>
channels
</RadioButtonGroup>
<PulsePattern v-if="pattern == 0"></PulsePattern>
<TravelPattern v-else-if="pattern == 1"></TravelPattern>
<StrobePattern v-else-if="pattern == 2"></StrobePattern>
</template>
<script>
import RadioButtonGroup from "./ui/RadioButtonGroup.vue";
import PulsePattern from "./patterns/Pulse.vue";
import StrobePattern from "./patterns/Strobe.vue";
import TravelPattern from "./patterns/Travel.vue";
const data = {
pattern: 1,
channel: 8,
};
export default {
name: "AutoMode",
components: {
RadioButtonGroup,
PulsePattern,
TravelPattern,
StrobePattern,
},
data() {
return data;
},
methods: {
patternSelectCallback: function (event) {
if (event.target.nodeName == "INPUT") {
this.pattern = Number(event.target.value);
console.log("p:" + this.pattern);
}
},
channelSelectCallback: function (event) {
if (event.target.nodeName == "INPUT") {
let selectedChannel = event.target.id.split("-")[0];
if (selectedChannel == "all") {
this.channel = 8;
} else {
this.channel = Number(selectedChannel);
}
console.log("c:" + this.channel);
}
},
},
};
</script>

View File

@ -1,34 +0,0 @@
<template>
<div class="row pt-2 gy-2">
<div class="col d-flex justify-content-center">
<form id="mode-input" class="btn-group" role="group" aria-label="Select mode">
<input type="radio" class="btn-check" name="mode-selection" id="test-mode" autocomplete="off" value="0" checked>
<input type="radio" class="btn-check" name="mode-selection" id="artnet-mode" autocomplete="off" value="1">
<input type="radio" class="btn-check" name="mode-selection" id="auto-mode" autocomplete="off" value="2">
<input type="radio" class="btn-check" name="mode-selection" id="manual-mode" autocomplete="off" value="3">
</form>
</div>
<div class="col d-flex justify-content-center">
<form id="pattern-input" class="btn-group" role="group" aria-label="Select pattern">
<input type="radio" class="btn-check" name="pattern-selection" id="pulse-pattern" autocomplete="off" value="0" checked>
<input type="radio" class="btn-check" name="pattern-selection" id="travel-pattern" autocomplete="off" value="1">
<input type="radio" class="btn-check" name="pattern-selection" id="strobe-pattern" autocomplete="off" value="2">
</form>
</div>
</div>
</template>
<script>
export default {
name: 'Reconnect',
props: {
msg: String
}
}
</script>
<style scoped>
#reconnectBackdrop{
visibility: hidden;
}
</style>

View File

@ -1,34 +0,0 @@
<template>
<div class="row pt-2 gy-2">
<div class="col d-flex justify-content-center">
<form id="mode-input" class="btn-group" role="group" aria-label="Select mode">
<div v-for="modename in mode_list" v-bind:key="modename">
<input :id="modename" type="radio" class="btn-check" name="mode-selection" autocomplete="off" value="0" checked>
</div>
</form>
</div>
<div class="col d-flex justify-content-center">
<form id="pattern-input" class="btn-group" role="group" aria-label="Select pattern">
<input type="radio" class="btn-check" name="pattern-selection" id="pulse-pattern" autocomplete="off" value="0" checked>
<input type="radio" class="btn-check" name="pattern-selection" id="travel-pattern" autocomplete="off" value="1">
<input type="radio" class="btn-check" name="pattern-selection" id="strobe-pattern" autocomplete="off" value="2">
</form>
</div>
</div>
</template>
<script>
export default {
name: 'ModeSelect',
props: {
mode_list: Array,
callback_list: Array
}
}
</script>
<style scoped>
#reconnectBackdrop{
visibility: hidden;
}
</style>

View File

@ -1,33 +0,0 @@
<template>
<div class="modal fade text-black" id="reconnectBackdrop" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="connectionLostLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="connectionLostLabel">Connection lost</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
Try reconnecting in <span id="time-left">60s</span>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" onclick="{timeout=1;}">Reconnect now</button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'Reconnect',
props: {
msg: String
}
}
</script>
<style scoped>
#reconnectBackdrop{
visibility: hidden;
}
</style>

View File

@ -0,0 +1,61 @@
<template>
<div
class="modal fade text-black show"
id="reconnectBackdrop"
data-bs-backdrop="static"
data-bs-keyboard="false"
tabindex="-1"
aria-labelledby="connectionLostLabel"
aria-hidden="true"
ref="reconnectBackdrop"
>
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="connectionLostLabel">Connection lost</h5>
<button
type="button"
class="btn-close"
data-bs-dismiss="modal"
aria-label="Close"
></button>
</div>
<div class="modal-body">
{{ isConnected }} <span id="time-left">60s</span>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" @click="reconnect">
Reconnect now
</button>
<button type="button" class="btn btn-primary">
{{ isConnected }}
</button>
</div>
</div>
</div>
</div>
</template>
<script>
const data = {};
export default {
name: "WSConnection",
data() {
return data;
},
props: { isConnected: Boolean },
created() {},
methods: {
log() {
console.log(this.socket);
},
},
};
</script>
<style scoped>
#reconnectBackdrop {
display: block;
}
</style>

View File

@ -0,0 +1,34 @@
<template>
<div class="row pt-2 px-0 gy-2 justify-content-center">
<div class="col-12 col-md">
<RangeSlider class="col" v-model:value="sensitivity">
sensitivity
</RangeSlider>
</div>
<div class="col-12 col-md">
<RangeSlider v-model:value="gravity"> gravity </RangeSlider>
</div>
</div>
</template>
<script>
import RangeSlider from "../ui/RangeSlider.vue";
export default {
name: "Pulse",
components: {
RangeSlider,
},
data() {
return { sensitivity: 70, gravity: 50 };
},
methods: {
sliderCallback: function (event) {
if (event.target.nodeName == "INPUT") {
console.log(event.target.id + ":" + this.channel);
}
},
},
};
</script>

View File

@ -0,0 +1,29 @@
<template>
<div class="row pt-2 px-0 gy-2 justify-content-center">
<div class="col-12 col-md">
<RangeSlider class="col" v-model:value="speed"> speed </RangeSlider>
</div>
</div>
</template>
<script>
import RangeSlider from "../ui/RangeSlider.vue";
export default {
name: "Strobe",
components: {
RangeSlider,
},
data() {
return { speed: 50 };
},
methods: {
sliderCallback: function (event) {
if (event.target.nodeName == "INPUT") {
console.log(event.target.id + ":" + this.channel);
}
},
},
};
</script>

View File

@ -0,0 +1,53 @@
<template>
<div class="row pt-2 px-0 gy-2 justify-content-center">
<div class="col-12 col-md">
<RangeSlider class="col" v-model:value="sensitivity">
sensitivity
</RangeSlider>
</div>
<div class="col-12 col-md">
<RangeSlider v-model:value="gravity"> gravity </RangeSlider>
</div>
</div>
<ColorAndModulation
:colorCallback="colorCallback"
:modulationCallback="modulationCallback"
ref="color_and_modulation"
></ColorAndModulation>
</template>
<script>
import ColorAndModulation from "../ui/ColorAndModulation.vue";
import RangeSlider from "../ui/RangeSlider.vue";
const data = {
sensitivity: 33,
gravity: 88,
};
export default {
name: "Travel",
components: {
ColorAndModulation,
RangeSlider,
},
data() {
return data;
},
methods: {
sliderCallback: function (event) {
if (event.target.nodeName == "INPUT") {
console.log(event.target.id + ":" + event.target.value);
}
},
colorCallback: function (color) {
console.log(color.hsv);
console.log(color.hsl);
},
modulationCallback: function (modulation) {
console.log(modulation);
},
},
};
</script>

View File

@ -0,0 +1,27 @@
<template>
<div class="form-check form-switch">
<input
class="form-check-input"
type="checkbox"
id="label"
:checked="is_checked"
@click="$emit('update:is_checked', !is_checked)"
/>
<label class="form-check-label text-capitalize" for="label">
{{ label }}
</label>
</div>
</template>
<script>
export default {
name: "CheckBox",
props: {
is_checked: Boolean,
},
setup(props, { slots }) {
const label = slots.default()[0].children.replace(/([A-Z_-])/g, " $1");
return { label };
},
};
</script>

View File

@ -0,0 +1,71 @@
<template>
<div class="row pt-2 gy-2 justify-content-center">
<div class="col-auto">
<CheckBox label="modulateColor" v-model:is_checked="modulateColor">
modulateColor
</CheckBox>
</div>
<div v-if="modulateColor" class="col-12 col-md">
<RangeSlider v-model:value="modulationSpeed">
modulation Speed
</RangeSlider>
</div>
<div v-if="modulateColor" class="w-100 d-block d-xxl-none"></div>
<div v-else class="w-100 d-block d-sm-none"></div>
<div class="col">
<ColorPicker
name="mainPicker"
:color_array="[color_array[0], color_array[1]]"
:callback="colorCallback"
></ColorPicker>
</div>
<div v-if="modulateColor" class="col">
<ColorPicker
name="modulationPicker"
:color_array="[color_array[2]]"
:callback="modulationCallback"
></ColorPicker>
</div>
</div>
</template>
<script>
import CheckBox from "./CheckBox.vue";
import ColorPicker from "./ColorPicker.vue";
import RangeSlider from "./RangeSlider.vue";
const data = {
modulateColor: true,
modulationSpeed: 50,
};
export default {
name: "ColorAndModulation",
props: {
color_array: {
type: Array,
default: function () {
return [
{ h: 180, s: 100, v: 50 },
{ h: 0, s: 80, v: 50 },
{ h: 60, s: 80, v: 50 },
];
},
},
colorCallback: { type: Function, required: true },
modulationCallback: { type: Function, required: true },
},
components: {
CheckBox,
ColorPicker,
RangeSlider,
},
data() {
return data;
},
};
</script>

View File

@ -0,0 +1,58 @@
<template>
<div :id="name" class="col d-flex justify-content-center overflow-auto"></div>
</template>
<script>
import iro from "@jaames/iro";
export default {
name: "ColorPicker",
props: {
name: String,
color_array: {
type: Array,
default: function () {
return [
{ h: 180, s: 100, v: 50 },
{ h: 0, s: 50, v: 50 },
];
},
},
callback: Function,
},
mounted() {
new iro.ColorPicker("#" + this.name, {
colors: this.color_array,
layout: [
{
component: iro.ui.Wheel,
options: {
wheelLightness: false,
},
},
{
component: iro.ui.Slider,
options: {
sliderType: "value",
},
},
],
layoutDirection: "horizontal",
width: 250,
margin: 0,
handleRadius: 14,
}).on("color:change", this.callback);
},
};
</script>
<style>
.IroColorPicker {
white-space: nowrap;
width: max-content;
}
.IroColorPicker > .IroWheel {
margin-right: 12px;
}
</style>

View File

@ -1,58 +0,0 @@
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<p>
For a guide and recipes on how to configure / customize this project,<br>
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
</p>
<h3>Installed CLI Plugins</h3>
<ul>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
</ul>
<h3>Essential Links</h3>
<ul>
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
</ul>
<h3>Ecosystem</h3>
<ul>
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
</ul>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>

View File

@ -0,0 +1,29 @@
<template>
<input
:id="id"
type="radio"
class="btn-check"
:name="group_label"
autocomplete="off"
:value="value"
:checked="is_checked"
/>
<label :for="id" class="btn btn-outline-primary text-capitalize">
<slot></slot>
</label>
</template>
<script>
export default {
name: "RadioButton",
props: {
group_label: String,
value: Number,
is_checked: Boolean,
},
setup(props, { slots }) {
const id = slots.default()[0].children + "-" + props.group_label;
return { id };
},
};
</script>

View File

@ -0,0 +1,44 @@
<template>
<legend v-if="$slots.default" class="col-auto text-capitalize fs-6 mb-0">
<slot></slot>
</legend>
<div class="col-auto">
<form
:id="group_name + '-input'"
class="btn-group"
role="group"
:aria-label="'Select' + group_name"
@click="callback"
>
<RadioButton
v-for="(label, index) in label_list"
:key="index"
:group_label="group_name"
:value="index"
:is_checked="index == checked_index"
>
{{ label }}
</RadioButton>
</form>
</div>
</template>
<script>
import RadioButton from "./RadioButton.vue";
export default {
name: "ModeSelect",
props: {
group_name: String,
label_list: Array,
checked_index: {
type: Number,
default: 0,
},
callback: Function,
},
components: {
RadioButton,
},
};
</script>

View File

@ -0,0 +1,41 @@
<template>
<div class="row">
<label
class="form-label col-9 col-sm-3 text-sm-end text-capitalize pr-1"
:for="id"
>
<slot></slot>
</label>
<span class="col-3 col-sm-1 text-end"> {{ value }} </span>
<div class="col">
<input
type="range"
class="form-range"
:id="id"
@input="sliderCallback"
:value="value"
/>
</div>
</div>
</template>
<script>
export default {
name: "RangeSlider",
props: {
value: Number,
},
methods: {
sliderCallback: function (event) {
if (event.target.nodeName == "INPUT") {
this.$emit("update:value", Number(event.target.value));
console.log("s:" + this.value);
}
},
},
setup(props, { slots }) {
const id = slots.default()[0].children + "-controlRange";
return { id };
},
};
</script>

View File

@ -1,6 +1,86 @@
import { createApp } from 'vue'
import App from './App.vue'
// import store from './store'
// import store from './storews'
// import VueNativeSock from "vue-native-websocket-vue3";
import "bootstrap/dist/css/bootstrap.min.css"
import "bootstrap"
// import { createStore } from "vuex";
createApp(App).mount('#app')
const app = createApp(App)
// const store = createStore({
// state: {
// socket: {
// // Connection Status
// isConnected: false,
// // Message content
// message: "",
// // Reconnect error
// reconnectError: false,
// // Heartbeat message sending time
// heartBeatInterval: 50000,
// // Heartbeat timer
// heartBeatTimer: 0
// }
// },
// mutations: {
// // Connection open
// SOCKET_ONOPEN(state, event) {
// console.log(event);
// app.config.globalProperties.$socket = event.currentTarget;
// state.socket.isConnected = true;
// // When the connection is successful, start sending heartbeat messages regularly to avoid being disconnected by the server
// // state.socket.heartBeatTimer = setInterval(() => {
// // const message = "Heartbeat message";
// // state.socket.isConnected &&
// // app.config.globalProperties.$socket.sendObj({
// // code: 200,
// // msg: message
// // });
// // }, state.socket.heartBeatInterval);
// },
// // Connection closed
// SOCKET_ONCLOSE(state, event) {
// state.socket.isConnected = false;
// // Stop the heartbeat message when the connection is closed
// clearInterval(state.socket.heartBeatTimer);
// state.socket.heartBeatTimer = 0;
// console.log("The line is disconnected: " + new Date());
// console.log(event);
// },
// // An error occurred
// SOCKET_ONERROR(state, event) {
// console.error(state, event);
// },
// // Receive the message sent by the server
// SOCKET_ONMESSAGE(state, message) {
// state.socket.message = message;
// },
// // Auto reconnect
// SOCKET_RECONNECT(state, count) {
// console.info("消息系统重连中...", state, count);
// },
// // Reconnect error
// SOCKET_RECONNECT_ERROR(state) {
// state.socket.reconnectError = true;
// }
// },
// modules: {}
// })
// app.use(store)
// app.use(VueNativeSock, "ws://192.168.4.1:8080", {
// store: store
// }, {
// reconnection: true, // (Boolean) whether to reconnect automatically (false)
// reconnectionAttempts: 90, // (Number) number of reconnection attempts before giving up (Infinity),
// reconnectionDelay: 10, // (Number) how long to initially wait before attempting a new (1000)
// });
app.mount('#app')
export default app

View File

@ -0,0 +1,100 @@
// import Vue from 'vue'
// import Vuex from 'vuex'
import { createStore } from "vuex";
// Vue.use(Vuex)
// root state object.
// each Vuex instance is just a single state tree.
const state = {
count: 0,
webSocket: WebSocket
}
// mutations are operations that actually mutate the state.
// each mutation handler gets the entire state tree as the
// first argument, followed by additional payload arguments.
// mutations must be synchronous and can be recorded by plugins
// for debugging purposes.
const mutations = {
increment(state) {
state.count++
},
decrement(state) {
state.count--
},
reconnect() {
state.webSocket = new WebSocket("ws://tropicananass.ovh:8080");
state.webSocket.onopen = function (event) {
console.log(`[open] ws: Connection established: ${event.code}`);
};
state.webSocket.onmessage = function (event) {
console.log(`[message] ws: Data received from server: ${event.data}`);
let message = event.data.split(":");
let command = message[0];
let payload = message[1];
console.log(command + ", " + payload);
switch (command) {
case 'm':
document.getElementById("mode-input").elements["mode-selection"][payload].checked = true;
break;
default:
break;
}
};
state.webSocket.webSocketonclose = function (event) {
if (event.wasClean) {
console.log(`[close] ws: Connection closed cleanly, code=${event.code} reason=${event.reason}`);
} else {
console.log('[close] ws: Connection died');
}
};
state.webSocket.onerror = function (error) {
console.log(`[error] ws: ${error.message}`);
};
}
}
// actions are functions that cause side effects and can involve
// asynchronous operations.
const actions = {
increment: ({ commit }) => commit('increment'),
decrement: ({ commit }) => commit('decrement'),
incrementIfOdd({ commit, state }) {
if ((state.count + 1) % 2 === 0) {
commit('increment')
}
},
incrementAsync({ commit }) {
return new Promise((resolve) => {
setTimeout(() => {
commit('increment')
resolve()
}, 1000)
})
}
}
// getters are functions.
const getters = {
evenOrOdd: state => state.count % 2 === 0 ? 'even' : 'odd',
isConnected: state => state.webSocket.readyState == WebSocket.OPEN
}
// A Vuex instance is created by combining the state, mutations, actions,
// and getters.
const store = createStore({
state,
getters,
actions,
mutations
})
export default store

View File

@ -0,0 +1,64 @@
import { createStore } from "vuex";
// import app from "main.js";
import { getCurrentInstance } from 'vue'
export default createStore({
state: {
socket: {
// Connection Status
isConnected: false,
// Message content
message: "",
// Reconnect error
reconnectError: false,
// Heartbeat message sending time
heartBeatInterval: 50000,
// Heartbeat timer
heartBeatTimer: 0
}
},
mutations: {
// Connection open
SOCKET_ONOPEN(state, event) {
console.log(event);
const app = getCurrentInstance()
app.config.globalProperties.$socket = event.currentTarget;
state.socket.isConnected = true;
// When the connection is successful, start sending heartbeat messages regularly to avoid being disconnected by the server
// state.socket.heartBeatTimer = setInterval(() => {
// const message = "Heartbeat message";
// state.socket.isConnected &&
// app.config.globalProperties.$socket.sendObj({
// code: 200,
// msg: message
// });
// }, state.socket.heartBeatInterval);
},
// Connection closed
SOCKET_ONCLOSE(state, event) {
state.socket.isConnected = false;
// Stop the heartbeat message when the connection is closed
clearInterval(state.socket.heartBeatTimer);
state.socket.heartBeatTimer = 0;
console.log("The line is disconnected: " + new Date());
console.log(event);
},
// An error occurred
SOCKET_ONERROR(state, event) {
console.error(state, event);
},
// Receive the message sent by the server
SOCKET_ONMESSAGE(state, message) {
state.socket.message = message;
},
// Auto reconnect
SOCKET_RECONNECT(state, count) {
console.info("消息系统重连中...", state, count);
},
// Reconnect error
SOCKET_RECONNECT_ERROR(state) {
state.socket.reconnectError = true;
}
},
modules: {}
});

View File

@ -0,0 +1,46 @@
const reconnect = function (options) {
console.log("connecting")
options.webSocket = new WebSocket("ws://192.168.4.1:8080");
options.webSocket.onopen = function (event) {
options.isConnected = true;
console.log(`[open] ws: Connection established: ${event.code}`);
};
options.webSocket.onmessage = function (event) {
console.log(`[message] ws: Data received from server: ${event.data}`);
};
options.webSocket.onclose = function (event) {
if (event.wasClean) {
console.log(
`[close] ws: Connection closed cleanly, code=${event.code} reason=${event.reason}`
);
} else {
console.log("[close] ws: Connection died");
}
options.isConnected = false;
};
options.webSocket.onerror = function (error) {
console.log(`[error] ws: ${error.message}`);
};
}
// const isConnected = function () {
// if (this.webSocket != null) {
// console.log(this.webSocket.readyState)
// return this.webSocket.readyState == WebSocket.OPEN
// }
// return false
// }
import { ref } from "vue"
// plugins/i18n.js
export default {
install: (app, options) => {
let webSocket = null;
app.config.globalProperties.webSocket = webSocket
app.config.globalProperties.reconnect = () => reconnect(options)
app.provide('ws', options)
app.provide('isConnected', ref(options.isConnected))
}
}

View File

@ -0,0 +1,14 @@
{
"folders": [
{
"path": "webapp"
},
{
"path": "vuex"
},
{
"path": "web"
}
],
"settings": {}
}