Added menu and expandable filters panel
This commit is contained in:
parent
50e56453da
commit
7491e85ecd
|
|
@ -23,6 +23,7 @@
|
|||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint-plugin-react": "^7.30.0",
|
||||
"prettier": "^2.5.1",
|
||||
"sass": "^1.54.5",
|
||||
"ts-node": "^8.10.1",
|
||||
"typescript": "^4.3.2",
|
||||
"vite": "^3.0.9",
|
||||
|
|
@ -1352,6 +1353,19 @@
|
|||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/anymatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
|
||||
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"normalize-path": "^3.0.0",
|
||||
"picomatch": "^2.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/arg": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
|
||||
|
|
@ -1476,6 +1490,15 @@
|
|||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/binary-extensions": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/binary-search-bounds": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/binary-search-bounds/-/binary-search-bounds-2.0.5.tgz",
|
||||
|
|
@ -1641,6 +1664,45 @@
|
|||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
|
||||
"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"anymatch": "~3.1.2",
|
||||
"braces": "~3.0.2",
|
||||
"glob-parent": "~5.1.2",
|
||||
"is-binary-path": "~2.1.0",
|
||||
"is-glob": "~4.0.1",
|
||||
"normalize-path": "~3.0.0",
|
||||
"readdirp": "~3.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.10.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar/node_modules/glob-parent": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"is-glob": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/clamp": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/clamp/-/clamp-1.0.1.tgz",
|
||||
|
|
@ -3869,6 +3931,12 @@
|
|||
"quantize": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/immutable": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.1.0.tgz",
|
||||
"integrity": "sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/import-fresh": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
||||
|
|
@ -3945,6 +4013,18 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-binary-path": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"binary-extensions": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-blob": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-blob/-/is-blob-2.1.0.tgz",
|
||||
|
|
@ -4616,6 +4696,15 @@
|
|||
"integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/normalize-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/normalize-svg-path": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-svg-path/-/normalize-svg-path-0.1.0.tgz",
|
||||
|
|
@ -5225,6 +5314,18 @@
|
|||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
|
||||
},
|
||||
"node_modules/readdirp": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"picomatch": "^2.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/regenerator-runtime": {
|
||||
"version": "0.13.9",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
|
||||
|
|
@ -5461,6 +5562,23 @@
|
|||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"node_modules/sass": {
|
||||
"version": "1.54.5",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.54.5.tgz",
|
||||
"integrity": "sha512-p7DTOzxkUPa/63FU0R3KApkRHwcVZYC0PLnLm5iyZACyp15qSi32x7zVUhRdABAATmkALqgGrjCJAcWvobmhHw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chokidar": ">=3.0.0 <4.0.0",
|
||||
"immutable": "^4.0.0",
|
||||
"source-map-js": ">=0.6.2 <2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"sass": "sass.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sax": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
|
||||
|
|
@ -7271,6 +7389,16 @@
|
|||
"color-convert": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"anymatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
|
||||
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"normalize-path": "^3.0.0",
|
||||
"picomatch": "^2.0.4"
|
||||
}
|
||||
},
|
||||
"arg": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
|
||||
|
|
@ -7369,6 +7497,12 @@
|
|||
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz",
|
||||
"integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg=="
|
||||
},
|
||||
"binary-extensions": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
|
||||
"dev": true
|
||||
},
|
||||
"binary-search-bounds": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/binary-search-bounds/-/binary-search-bounds-2.0.5.tgz",
|
||||
|
|
@ -7490,6 +7624,33 @@
|
|||
"supports-color": "^5.3.0"
|
||||
}
|
||||
},
|
||||
"chokidar": {
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
|
||||
"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"anymatch": "~3.1.2",
|
||||
"braces": "~3.0.2",
|
||||
"fsevents": "~2.3.2",
|
||||
"glob-parent": "~5.1.2",
|
||||
"is-binary-path": "~2.1.0",
|
||||
"is-glob": "~4.0.1",
|
||||
"normalize-path": "~3.0.0",
|
||||
"readdirp": "~3.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"glob-parent": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-glob": "^4.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"clamp": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/clamp/-/clamp-1.0.1.tgz",
|
||||
|
|
@ -9199,6 +9360,12 @@
|
|||
"quantize": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"immutable": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.1.0.tgz",
|
||||
"integrity": "sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==",
|
||||
"dev": true
|
||||
},
|
||||
"import-fresh": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
||||
|
|
@ -9260,6 +9427,15 @@
|
|||
"has-bigints": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"is-binary-path": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"binary-extensions": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"is-blob": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-blob/-/is-blob-2.1.0.tgz",
|
||||
|
|
@ -9767,6 +9943,12 @@
|
|||
"integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==",
|
||||
"dev": true
|
||||
},
|
||||
"normalize-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
||||
"dev": true
|
||||
},
|
||||
"normalize-svg-path": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-svg-path/-/normalize-svg-path-0.1.0.tgz",
|
||||
|
|
@ -10227,6 +10409,15 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"readdirp": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"picomatch": "^2.2.1"
|
||||
}
|
||||
},
|
||||
"regenerator-runtime": {
|
||||
"version": "0.13.9",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
|
||||
|
|
@ -10411,6 +10602,17 @@
|
|||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"sass": {
|
||||
"version": "1.54.5",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.54.5.tgz",
|
||||
"integrity": "sha512-p7DTOzxkUPa/63FU0R3KApkRHwcVZYC0PLnLm5iyZACyp15qSi32x7zVUhRdABAATmkALqgGrjCJAcWvobmhHw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chokidar": ">=3.0.0 <4.0.0",
|
||||
"immutable": "^4.0.0",
|
||||
"source-map-js": ">=0.6.2 <2.0.0"
|
||||
}
|
||||
},
|
||||
"sax": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint-plugin-react": "^7.30.0",
|
||||
"prettier": "^2.5.1",
|
||||
"sass": "^1.54.5",
|
||||
"ts-node": "^8.10.1",
|
||||
"typescript": "^4.3.2",
|
||||
"vite": "^3.0.9",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
.dashboard-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: auto;
|
||||
|
||||
.spacer {
|
||||
margin: 0 1rem;
|
||||
width: 1px;
|
||||
background: var(--header-spacer-color);
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.filter-button {
|
||||
font-size: 125%;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
.filters-panel {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 2;
|
||||
width: 20rem;
|
||||
display: flex;
|
||||
|
||||
.inner {
|
||||
background-color: var(--box-bg-color);
|
||||
padding: 0.25rem 0.5rem;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.shadow {
|
||||
background: var(--filters-menu-shadow);
|
||||
width: 8px;
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
.filter-close {
|
||||
cursor: pointer;
|
||||
font-size: 150%;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg>
|
||||
|
After Width: | Height: | Size: 218 B |
|
|
@ -0,0 +1 @@
|
|||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z"></path></svg>
|
||||
|
After Width: | Height: | Size: 349 B |
|
|
@ -0,0 +1 @@
|
|||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path></svg>
|
||||
|
After Width: | Height: | Size: 221 B |
|
|
@ -0,0 +1 @@
|
|||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path></svg>
|
||||
|
After Width: | Height: | Size: 224 B |
|
|
@ -1,9 +1,9 @@
|
|||
:root {
|
||||
--main-bg-color: #eee;
|
||||
--main-bg-color: #eee;
|
||||
--main-fg-color: #000;
|
||||
--button-bg-color: #3988FF;
|
||||
--button-bg-color: #3988ff;
|
||||
--button-fg-color: #fff;
|
||||
--button-hover-bg-color: #0F6FFF;
|
||||
--button-hover-bg-color: #0f6fff;
|
||||
--button-hover-fg-color: #fff;
|
||||
--button-cancel-bg-color: transparent;
|
||||
--button-cancel-fg-color: #000;
|
||||
|
|
@ -11,24 +11,27 @@
|
|||
--button-remove-fg-color: #f00;
|
||||
--header-bg-color: #fff;
|
||||
--header-spacer-color: #ddd;
|
||||
--header-shadow: linear-gradient(0deg, rgba(255,255,255,0) 5%, rgba(190,190,190,0.6) 100%);
|
||||
--header-shadow: linear-gradient(0deg, rgba(255, 255, 255, 0) 5%, rgba(190, 190, 190, 0.6) 100%);
|
||||
--box-bg-color: #fff;
|
||||
--box-fg-color: #111;
|
||||
--box-loader-bg-color: rgba(128, 128, 128, 0.3);
|
||||
--box-loader-fg-color: #fff;
|
||||
--box-action-fg-color: #666;
|
||||
--box-preview-bg-color: #3988FF;
|
||||
--box-shadow: 0px 10px 15px -3px rgba(0,0,0,0.1);
|
||||
--modal-overlay-bg-color: rgba(0,0,0,0.2);
|
||||
--box-preview-bg-color: #3988ff;
|
||||
--box-shadow: 0px 10px 15px -3px rgba(0, 0, 0, 0.1);
|
||||
--modal-overlay-bg-color: rgba(0, 0, 0, 0.2);
|
||||
--graph-axis-fg-color: #777;
|
||||
--graph-grid-color: rgb(238, 238, 238);
|
||||
--link-fg-color: #3988ff;
|
||||
--menu-shadow: linear-gradient(270deg, rgba(255, 255, 255, 0) 0%, rgba(90, 90, 90, 0.2) 100%);
|
||||
--filters-menu-shadow: linear-gradient(90deg, rgba(255, 255, 255, 0) 0%, rgba(90, 90, 90, 0.2) 100%);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--main-bg-color: #222;
|
||||
--main-fg-color: #b8b8b8;
|
||||
--header-bg-color: #000;
|
||||
--main-fg-color: #ccc;
|
||||
--header-bg-color: #111;
|
||||
--header-spacer-color: #333;
|
||||
--header-shadow: none;
|
||||
--box-bg-color: #111;
|
||||
|
|
@ -36,10 +39,24 @@
|
|||
--box-shadow: none;
|
||||
--graph-axis-fg-color: #666;
|
||||
--graph-grid-color: rgb(25, 25, 25);
|
||||
--button-bg-color: #0b3c9f;
|
||||
--button-fg-color: #eee;
|
||||
--button-cancel-fg-color: #ccc;
|
||||
}
|
||||
|
||||
select,
|
||||
input {
|
||||
border: 1px solid #333;
|
||||
background: #222;
|
||||
color: #ccc;
|
||||
border-radius: 0.25rem;
|
||||
box-sizing: border-box;
|
||||
padding: 0.1rem 0.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
body, html {
|
||||
body,
|
||||
html {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
|
|
@ -49,11 +66,13 @@ body, html {
|
|||
body {
|
||||
background: var(--main-bg-color);
|
||||
color: var(--main-fg-color);
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
||||
}
|
||||
|
||||
button, input, select {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
button,
|
||||
input,
|
||||
select {
|
||||
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
||||
}
|
||||
|
||||
button {
|
||||
|
|
@ -72,11 +91,16 @@ button:hover {
|
|||
color: var(--button-hover-fg-color);
|
||||
}
|
||||
|
||||
input, select {
|
||||
input,
|
||||
select {
|
||||
padding: 0.15rem 0.4rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--link-fg-color);
|
||||
}
|
||||
|
||||
.login {
|
||||
width: 300px;
|
||||
margin: 2rem auto;
|
||||
|
|
@ -93,6 +117,95 @@ input, select {
|
|||
flex-direction: column;
|
||||
}
|
||||
|
||||
main.layout {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.menu {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
color: var(--box-fg-color);
|
||||
width: 20rem;
|
||||
transition: left 0.1s;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.menu .inner {
|
||||
background-color: var(--box-bg-color);
|
||||
flex: 1;
|
||||
padding: 0.25em 0.5rem;
|
||||
}
|
||||
|
||||
.menu .shadow {
|
||||
width: 8px;
|
||||
background: var(--menu-shadow);
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
.menu .menu-close {
|
||||
font-size: 150%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.menu nav {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.menu nav a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.menu-overlay {
|
||||
background-color: var(--modal-overlay-bg-color);
|
||||
z-index: 1;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
header.header {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
header.header > .inner {
|
||||
display: flex;
|
||||
padding: 0.5rem 0.5rem;
|
||||
background-color: var(--header-bg-color);
|
||||
}
|
||||
|
||||
header.header > .shadow {
|
||||
position: absolute;
|
||||
background: var(--header-shadow);
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
header.header > .inner > .menu-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 125%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
section.content {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sensors {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
|
|
@ -103,9 +216,9 @@ input, select {
|
|||
}
|
||||
|
||||
@media only screen and (max-width: 1200px) {
|
||||
.sensors {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.sensors {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.box {
|
||||
|
|
@ -168,6 +281,10 @@ form .input {
|
|||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
form .input label {
|
||||
margin-bottom: 0.2rem;
|
||||
}
|
||||
|
||||
form .actions {
|
||||
text-align: right;
|
||||
margin-top: 1rem;
|
||||
|
|
@ -198,35 +315,12 @@ form.horizontal .input label {
|
|||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.dashboard-head .inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
padding: 0.5rem 0.5rem;
|
||||
background-color: var(--header-bg-color);
|
||||
}
|
||||
|
||||
.dashboard-head .shadow {
|
||||
position: absolute;
|
||||
background: var(--header-shadow);
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.dashboard-head .spacer {
|
||||
margin: 0 1rem;
|
||||
width: 1px;
|
||||
background: var(--header-spacer-color);
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.checkbox-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.checkbox-label input[type=checkbox] {
|
||||
.checkbox-label input[type="checkbox"] {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
|
|
@ -235,7 +329,7 @@ form.horizontal .input label {
|
|||
padding: 0.25rem;
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
min-height: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.grid-sensors {
|
||||
|
|
@ -308,7 +402,7 @@ form.horizontal .input label {
|
|||
|
||||
.grid-sensors .grid-box .box .resize {
|
||||
position: absolute;
|
||||
right: 0;;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
|
|
@ -394,6 +488,13 @@ form.horizontal .input label {
|
|||
}
|
||||
|
||||
@keyframes rotate {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(-360deg); }
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(-360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@import "components/dashboard-header";
|
||||
@import "components/filters-panel";
|
||||
|
|
@ -1,2 +1,6 @@
|
|||
export { ReactComponent as SettingsIcon } from '@/assets/icons/settings.svg'
|
||||
export { ReactComponent as RefreshIcon } from '@/assets/icons/refresh.svg'
|
||||
export { ReactComponent as MenuIcon } from '@/assets/icons/menu.svg'
|
||||
export { ReactComponent as CancelIcon } from '@/assets/icons/cancel.svg'
|
||||
export { ReactComponent as FiltersIcon } from '@/assets/icons/filters.svg'
|
||||
export { ReactComponent as PlusIcon } from '@/assets/icons/plus.svg'
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import { Root } from './Root'
|
||||
import { render } from 'preact'
|
||||
|
||||
import './assets/style.css'
|
||||
import './assets/style.scss'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
render(<Root />, document.getElementById('application')!)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
import { MenuIcon } from '@/icons'
|
||||
import { cn } from '@/utils/cn'
|
||||
import { ComponentChild } from 'preact'
|
||||
import { useState } from 'preact/hooks'
|
||||
import { UserMenu } from './components/UserMenu'
|
||||
|
||||
type Props = {
|
||||
children: ComponentChild
|
||||
header?: ComponentChild
|
||||
className?: string
|
||||
}
|
||||
|
||||
export const UserLayout = ({ children, header, className }: Props) => {
|
||||
const [menuShown, setMenuShown] = useState(false)
|
||||
|
||||
return (
|
||||
<main className={cn('layout', className)}>
|
||||
<header className={'header'}>
|
||||
<div className="inner">
|
||||
<div className="menu-button" onClick={() => setMenuShown(true)}>
|
||||
<MenuIcon />
|
||||
</div>
|
||||
{header}
|
||||
</div>
|
||||
<div className="shadow"></div>
|
||||
</header>
|
||||
<section className="content">{children}</section>
|
||||
<UserMenu shown={menuShown} onHide={() => setMenuShown(false)} />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import { CancelIcon } from '@/icons'
|
||||
|
||||
type Props = {
|
||||
shown: boolean
|
||||
onHide: () => void
|
||||
}
|
||||
|
||||
export const UserMenu = ({ shown, onHide }: Props) => {
|
||||
return (
|
||||
<>
|
||||
<div className="menu" style={{ left: !shown ? '-20rem' : '0' }}>
|
||||
<div className="inner">
|
||||
<div className="menu-close" onClick={onHide}>
|
||||
<CancelIcon />
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<a href="#">Dashboards</a>
|
||||
<a href="#">Sensors</a>
|
||||
<a href="#">Settings</a>
|
||||
</nav>
|
||||
</div>
|
||||
<div className="shadow"></div>
|
||||
</div>
|
||||
{shown && <div className="menu-overlay" onClick={onHide}></div>}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -3,12 +3,13 @@ import {
|
|||
getDashboards,
|
||||
updateDashboard,
|
||||
} from '@/api/dashboards'
|
||||
import { UserLayout } from '@/layouts/UserLayout/UserLayout'
|
||||
import { createDashboardContent } from '@/utils/createDashboardContent'
|
||||
import { parseDashboard } from '@/utils/parseDashboard'
|
||||
import { useEffect, useMemo, useState } from 'preact/hooks'
|
||||
import { useQuery, useQueryClient } from 'react-query'
|
||||
import { DashboardGrid } from './components/DashboardGrid'
|
||||
import { DashboardHeader } from './components/DashboardHeader'
|
||||
import { DashboardGrid } from './components/DashboardGrid/DashboardGrid'
|
||||
import { DashboardHeader } from './components/DashboardHeader/DashboardHeader'
|
||||
import { GRID_H_SNAP, GRID_WIDTH } from './constants'
|
||||
import { DashboardContextProvider } from './contexts/DashboardContext'
|
||||
import { BoxDefinition } from './types'
|
||||
|
|
@ -82,10 +83,14 @@ export const NewDashboardPage = () => {
|
|||
|
||||
return (
|
||||
<DashboardContextProvider>
|
||||
<div className="dashboard">
|
||||
<DashboardHeader onRefresh={handleRefresh} onNewBox={handleNewBox} />
|
||||
<UserLayout
|
||||
className="dashboard"
|
||||
header={
|
||||
<DashboardHeader onRefresh={handleRefresh} onNewBox={handleNewBox} />
|
||||
}
|
||||
>
|
||||
<DashboardGrid boxes={boxes} onChange={handleChange} />
|
||||
</div>
|
||||
</UserLayout>
|
||||
</DashboardContextProvider>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { BoxDefinition } from '../types'
|
||||
import { normalizeBoxes } from '../utils/normalizeBoxes'
|
||||
import { EditableBox } from './EditableBox'
|
||||
import { BoxDefinition } from '../../types'
|
||||
import { normalizeBoxes } from '../../utils/normalizeBoxes'
|
||||
import { EditableBox } from './components/EditableBox'
|
||||
|
||||
type Props = {
|
||||
boxes: BoxDefinition[]
|
||||
|
|
@ -3,8 +3,8 @@ import { DashboardDialData } from '@/utils/parseDashboard'
|
|||
import { RefObject } from 'preact'
|
||||
import { useMemo } from 'preact/hooks'
|
||||
import { useQuery } from 'react-query'
|
||||
import { useDashboardContext } from '../contexts/DashboardContext'
|
||||
import { BoxDefinition } from '../types'
|
||||
import { useDashboardContext } from '../../../contexts/DashboardContext'
|
||||
import { BoxDefinition } from '../../../types'
|
||||
import { BoxLoader } from './BoxLoader'
|
||||
|
||||
type Props = {
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
import { getSensorValues } from '@/api/sensorValues'
|
||||
import { useDashboardContext } from '@/pages/dashboard/contexts/DashboardContext'
|
||||
import { BoxDefinition } from '@/pages/dashboard/types'
|
||||
import { max } from '@/utils/max'
|
||||
import { min } from '@/utils/min'
|
||||
import { DashboardGraphData } from '@/utils/parseDashboard'
|
||||
import { RefObject } from 'preact'
|
||||
import { useEffect, useRef } from 'preact/hooks'
|
||||
import { useQuery } from 'react-query'
|
||||
import { useDashboardContext } from '../contexts/DashboardContext'
|
||||
import { BoxDefinition } from '../types'
|
||||
import { BoxLoader } from './BoxLoader'
|
||||
|
||||
type Props = {
|
||||
|
|
@ -1,15 +1,15 @@
|
|||
import { useWindowEvent } from '@/utils/hooks/useWindowEvent'
|
||||
import { useRef, useState } from 'preact/hooks'
|
||||
import { GRID_WIDTH } from '../constants'
|
||||
import { useDashboardContext } from '../contexts/DashboardContext'
|
||||
import { useDragging } from '../hooks/useDragging'
|
||||
import { ResizingMode, useResize } from '../hooks/useResize'
|
||||
import { BoxDefinition } from '../types'
|
||||
import { BoxDialContent } from './BoxDialContent'
|
||||
import { BoxGraphContent } from './BoxGraphContent'
|
||||
import { BoxSettings } from './BoxSettings/BoxSettings'
|
||||
import { useElementOffsets } from '@/utils/hooks/useElementOffsets'
|
||||
import { RefreshIcon, SettingsIcon } from '@/icons'
|
||||
import { BoxDefinition } from '@/pages/dashboard/types'
|
||||
import { GRID_WIDTH } from '@/pages/dashboard/constants'
|
||||
import { useDashboardContext } from '@/pages/dashboard/contexts/DashboardContext'
|
||||
import { useDragging } from '@/pages/dashboard/hooks/useDragging'
|
||||
import { useResize, ResizingMode } from '@/pages/dashboard/hooks/useResize'
|
||||
import { BoxSettings } from '../../BoxSettings/BoxSettings'
|
||||
|
||||
type Props = {
|
||||
box: BoxDefinition
|
||||
|
|
@ -54,6 +54,10 @@ export const EditableBox = ({
|
|||
const handleMouseDown = (e: MouseEvent) => {
|
||||
e.preventDefault()
|
||||
|
||||
if (verticalMode) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!dragging.active && boxRef) {
|
||||
const pos = {
|
||||
top: boxRef.offsetTop,
|
||||
|
|
@ -74,6 +78,10 @@ export const EditableBox = ({
|
|||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
if (verticalMode) {
|
||||
return
|
||||
}
|
||||
|
||||
if (resizing.mode === ResizingMode.NONE) {
|
||||
setResizing({
|
||||
mode: target,
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
import { RefreshIcon } from '@/icons'
|
||||
import { Filters } from './Filters'
|
||||
|
||||
type Props = {
|
||||
onNewBox: () => void
|
||||
onRefresh: () => void
|
||||
}
|
||||
|
||||
export const DashboardHeader = ({ onNewBox, onRefresh }: Props) => {
|
||||
return (
|
||||
<div className="dashboard-head">
|
||||
<div className="inner">
|
||||
<button onClick={onNewBox}>Add box</button>
|
||||
<div className="spacer" />
|
||||
<Filters />
|
||||
<div className="spacer" />
|
||||
<button onClick={onRefresh}>
|
||||
<RefreshIcon /> Refresh all
|
||||
</button>
|
||||
</div>
|
||||
<div className="shadow"></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
import { CancelIcon, FiltersIcon, PlusIcon, RefreshIcon } from '@/icons'
|
||||
import { useState } from 'preact/hooks'
|
||||
import { useDashboardContext } from '../../contexts/DashboardContext'
|
||||
import { DashboardFilters } from './components/DashboardFilters'
|
||||
|
||||
type Props = {
|
||||
onNewBox: () => void
|
||||
onRefresh: () => void
|
||||
}
|
||||
|
||||
export const DashboardHeader = ({ onNewBox, onRefresh }: Props) => {
|
||||
const { verticalMode } = useDashboardContext()
|
||||
const [filtersShown, setFiltersShown] = useState(false)
|
||||
|
||||
return (
|
||||
<div className="dashboard-head">
|
||||
{verticalMode && (
|
||||
<>
|
||||
<button onClick={onNewBox}>
|
||||
<PlusIcon />
|
||||
</button>
|
||||
<button onClick={onRefresh}>
|
||||
<RefreshIcon />
|
||||
</button>
|
||||
<div className="filter-button" onClick={() => setFiltersShown(true)}>
|
||||
<FiltersIcon />
|
||||
</div>
|
||||
|
||||
{filtersShown && (
|
||||
<div className="filters-panel">
|
||||
<div className="shadow" />
|
||||
<div className="inner">
|
||||
<div
|
||||
className="filter-close"
|
||||
onClick={() => setFiltersShown(false)}
|
||||
>
|
||||
<CancelIcon />
|
||||
</div>
|
||||
|
||||
<DashboardFilters />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{!verticalMode && (
|
||||
<>
|
||||
<button onClick={onNewBox}>Add box</button>
|
||||
<div className="spacer" />
|
||||
<DashboardFilters />
|
||||
<div className="spacer" />
|
||||
<button onClick={onRefresh}>
|
||||
<RefreshIcon /> Refresh all
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { DateTimeInput } from '@/components/DateTimeInput'
|
||||
import { intervalToRange } from '@/utils/intervalToRange'
|
||||
import { useEffect, useState } from 'preact/hooks'
|
||||
import { useDashboardContext } from '../contexts/DashboardContext'
|
||||
import { useDashboardContext } from '../../../contexts/DashboardContext'
|
||||
|
||||
export type FilterInterval =
|
||||
| 'hour'
|
||||
|
|
@ -17,8 +17,8 @@ export type FilterValue = {
|
|||
customTo: Date
|
||||
}
|
||||
|
||||
export const Filters = () => {
|
||||
const { filter: preset, setFilter } = useDashboardContext()
|
||||
export const DashboardFilters = () => {
|
||||
const { filter: preset, setFilter, verticalMode } = useDashboardContext()
|
||||
|
||||
const [value, setValue] = useState(preset)
|
||||
|
||||
|
|
@ -50,7 +50,10 @@ export const Filters = () => {
|
|||
|
||||
return (
|
||||
<div className="filter-form">
|
||||
<form className="horizontal" onSubmit={handleSubmit}>
|
||||
<form
|
||||
className={!verticalMode ? 'horizontal' : undefined}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<div className="input">
|
||||
<label>Interval</label>
|
||||
<select
|
||||
|
|
@ -1,99 +0,0 @@
|
|||
import { SensorInfo } from '@/api/sensors'
|
||||
import { getSensorValues } from '@/api/sensorValues'
|
||||
import { useEffect, useRef } from 'preact/hooks'
|
||||
import { useQuery } from 'react-query'
|
||||
import { FilterValue } from './Filters'
|
||||
|
||||
type Props = {
|
||||
sensor: SensorInfo
|
||||
filter: FilterValue
|
||||
onEdit: (sensor: SensorInfo) => void
|
||||
}
|
||||
|
||||
export const Sensor = ({ sensor, filter, onEdit }: Props) => {
|
||||
const bodyRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const valuesQuery = {
|
||||
sensor: sensor.sensor,
|
||||
from: filter.customFrom,
|
||||
to: filter.customTo,
|
||||
}
|
||||
|
||||
const values = useQuery(['/sensor/values', valuesQuery], () =>
|
||||
getSensorValues(valuesQuery)
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
// TODO: These should be probably returned by server, could be outdated
|
||||
const from = filter.customFrom
|
||||
const to = filter.customTo
|
||||
const minValue = parseFloat(sensor.config.min)
|
||||
const maxValue = parseFloat(sensor.config.max)
|
||||
const customRange = !isNaN(minValue) && !isNaN(maxValue)
|
||||
|
||||
if (bodyRef.current && values.data) {
|
||||
window.Plotly.newPlot(
|
||||
bodyRef.current,
|
||||
[
|
||||
{
|
||||
...(sensor.config.graphType === 'line' && {
|
||||
type: 'scatter',
|
||||
mode: 'lines',
|
||||
}),
|
||||
...(sensor.config.graphType === 'points' && {
|
||||
type: 'scatter',
|
||||
mode: 'markers',
|
||||
}),
|
||||
...(sensor.config.graphType === 'lineAndPoints' && {
|
||||
type: 'scatter',
|
||||
mode: 'lines+markers',
|
||||
}),
|
||||
...(sensor.config.graphType === 'bar' && { type: 'bar' }),
|
||||
x: values.data.map((v) => new Date(v.timestamp * 1000)),
|
||||
y: values.data.map((v) => v.value),
|
||||
line: {
|
||||
width: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
{
|
||||
xaxis: { range: [from, to], type: 'date' },
|
||||
yaxis: {
|
||||
...(customRange && { range: [minValue, maxValue] }),
|
||||
...(sensor.config.unit && { ticksuffix: ` ${sensor.config.unit}` }),
|
||||
},
|
||||
margin: {
|
||||
l: 70,
|
||||
r: 20,
|
||||
b: 60,
|
||||
t: 20,
|
||||
pad: 5,
|
||||
},
|
||||
height: 300,
|
||||
},
|
||||
{
|
||||
responsive: true,
|
||||
}
|
||||
)
|
||||
}
|
||||
}, [values.data, sensor.config])
|
||||
|
||||
return (
|
||||
<div className="sensor">
|
||||
<div className="header">
|
||||
<div className="name">{sensor.config?.name ?? sensor.sensor}</div>
|
||||
<div className="actions">
|
||||
<button className="config" onClick={() => onEdit(sensor)}>
|
||||
Config
|
||||
</button>
|
||||
<button className="refresh" onClick={() => values.refetch()}>
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="body">
|
||||
<div ref={bodyRef} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ import { useViewportSize } from '@/utils/hooks/useViewportSize'
|
|||
import { intervalToRange } from '@/utils/intervalToRange'
|
||||
import { ComponentChild, createContext } from 'preact'
|
||||
import { StateUpdater, useContext, useMemo, useState } from 'preact/hooks'
|
||||
import { FilterValue } from '../components/Filters'
|
||||
import { FilterValue } from '../components/DashboardHeader/components/DashboardFilters'
|
||||
|
||||
type DashboardContextType = {
|
||||
filter: FilterValue
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
export const cn = (...cns: (string | undefined | null | boolean)[]) =>
|
||||
cns.filter(Boolean).join(' ')
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { FilterInterval } from '@/pages/dashboard/components/Filters'
|
||||
import { FilterInterval } from '@/pages/dashboard/components/DashboardHeader/components/DashboardFilters'
|
||||
|
||||
export const intervalToRange = (
|
||||
interval: FilterInterval,
|
||||
|
|
|
|||
Loading…
Reference in New Issue