mirror of
				https://github.com/blakeblackshear/frigate.git
				synced 2025-10-27 10:52:11 +01:00 
			
		
		
		
	Scrubber UI component (#9036)
* add scrubber and ui playground when running dev * fix mobile dropdown menu width * timeline scrubber and revamp for all event handlers
This commit is contained in:
		
							parent
							
								
									2236ae5d3b
								
							
						
					
					
						commit
						7bec162353
					
				
							
								
								
									
										144
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										144
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							@ -48,6 +48,7 @@
 | 
				
			|||||||
        "tailwindcss-animate": "^1.0.7",
 | 
					        "tailwindcss-animate": "^1.0.7",
 | 
				
			||||||
        "video.js": "^8.6.1",
 | 
					        "video.js": "^8.6.1",
 | 
				
			||||||
        "videojs-playlist": "^5.1.0",
 | 
					        "videojs-playlist": "^5.1.0",
 | 
				
			||||||
 | 
					        "vis-timeline": "^7.7.3",
 | 
				
			||||||
        "vite-plugin-monaco-editor": "^1.1.0",
 | 
					        "vite-plugin-monaco-editor": "^1.1.0",
 | 
				
			||||||
        "zod": "^3.22.4"
 | 
					        "zod": "^3.22.4"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
@ -215,6 +216,18 @@
 | 
				
			|||||||
      "resolved": "https://registry.npmjs.org/@cycjimmy/jsmpeg-player/-/jsmpeg-player-6.0.5.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@cycjimmy/jsmpeg-player/-/jsmpeg-player-6.0.5.tgz",
 | 
				
			||||||
      "integrity": "sha512-bVNHQ7VN9ecKT5AI/6RC7zpW/y4ca68a9txeR5Wiin+jKpUn/7buMe+5NPub89A8NNeNnKPQfrD2+c76ch36mA=="
 | 
					      "integrity": "sha512-bVNHQ7VN9ecKT5AI/6RC7zpW/y4ca68a9txeR5Wiin+jKpUn/7buMe+5NPub89A8NNeNnKPQfrD2+c76ch36mA=="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/@egjs/hammerjs": {
 | 
				
			||||||
 | 
					      "version": "2.0.17",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==",
 | 
				
			||||||
 | 
					      "peer": true,
 | 
				
			||||||
 | 
					      "dependencies": {
 | 
				
			||||||
 | 
					        "@types/hammerjs": "^2.0.36"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "node": ">=0.8.0"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/@esbuild/android-arm": {
 | 
					    "node_modules/@esbuild/android-arm": {
 | 
				
			||||||
      "version": "0.19.8",
 | 
					      "version": "0.19.8",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.8.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.8.tgz",
 | 
				
			||||||
@ -2253,6 +2266,12 @@
 | 
				
			|||||||
      "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==",
 | 
					      "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==",
 | 
				
			||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/@types/hammerjs": {
 | 
				
			||||||
 | 
					      "version": "2.0.45",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.45.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-qkcUlZmX6c4J8q45taBKTL3p+LbITgyx7qhlPYOdOHZB7B31K0mXbP5YA7i7SgDeEGuI9MnumiKPEMrxg8j3KQ==",
 | 
				
			||||||
 | 
					      "peer": true
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/@types/istanbul-lib-coverage": {
 | 
					    "node_modules/@types/istanbul-lib-coverage": {
 | 
				
			||||||
      "version": "2.0.6",
 | 
					      "version": "2.0.6",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
 | 
				
			||||||
@ -3439,6 +3458,15 @@
 | 
				
			|||||||
        "node": ">= 6"
 | 
					        "node": ">= 6"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/component-emitter": {
 | 
				
			||||||
 | 
					      "version": "1.3.1",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==",
 | 
				
			||||||
 | 
					      "peer": true,
 | 
				
			||||||
 | 
					      "funding": {
 | 
				
			||||||
 | 
					        "url": "https://github.com/sponsors/sindresorhus"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/concat-map": {
 | 
					    "node_modules/concat-map": {
 | 
				
			||||||
      "version": "0.0.1",
 | 
					      "version": "0.0.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
 | 
				
			||||||
@ -3498,6 +3526,12 @@
 | 
				
			|||||||
        "node": ">=4"
 | 
					        "node": ">=4"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/cssfilter": {
 | 
				
			||||||
 | 
					      "version": "0.0.10",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==",
 | 
				
			||||||
 | 
					      "peer": true
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/cssstyle": {
 | 
					    "node_modules/cssstyle": {
 | 
				
			||||||
      "version": "3.0.0",
 | 
					      "version": "3.0.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-3.0.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-3.0.0.tgz",
 | 
				
			||||||
@ -5184,6 +5218,12 @@
 | 
				
			|||||||
      "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz",
 | 
				
			||||||
      "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w=="
 | 
					      "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w=="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/keycharm": {
 | 
				
			||||||
 | 
					      "version": "0.4.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/keycharm/-/keycharm-0.4.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-TyQTtsabOVv3MeOpR92sIKk/br9wxS+zGj4BG7CR8YbK4jM3tyIBaF0zhzeBUMx36/Q/iQLOKKOT+3jOQtemRQ==",
 | 
				
			||||||
 | 
					      "peer": true
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/keycode": {
 | 
					    "node_modules/keycode": {
 | 
				
			||||||
      "version": "2.2.0",
 | 
					      "version": "2.2.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz",
 | 
				
			||||||
@ -5500,6 +5540,15 @@
 | 
				
			|||||||
        "node": ">= 8"
 | 
					        "node": ">= 8"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/moment": {
 | 
				
			||||||
 | 
					      "version": "2.29.4",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
 | 
				
			||||||
 | 
					      "peer": true,
 | 
				
			||||||
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "node": "*"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/monaco-editor": {
 | 
					    "node_modules/monaco-editor": {
 | 
				
			||||||
      "version": "0.44.0",
 | 
					      "version": "0.44.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.44.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.44.0.tgz",
 | 
				
			||||||
@ -6291,6 +6340,15 @@
 | 
				
			|||||||
        "node": ">= 0.6.0"
 | 
					        "node": ">= 0.6.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/propagating-hammerjs": {
 | 
				
			||||||
 | 
					      "version": "2.0.1",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/propagating-hammerjs/-/propagating-hammerjs-2.0.1.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-PH3zG5whbSxMocphXJzVtvKr+vWAgfkqVvtuwjSJ/apmEACUoiw6auBAT5HYXpZOR0eGcTAfYG5Yl8h91O5Elg==",
 | 
				
			||||||
 | 
					      "peer": true,
 | 
				
			||||||
 | 
					      "peerDependencies": {
 | 
				
			||||||
 | 
					        "@egjs/hammerjs": "^2.0.17"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/proxy-from-env": {
 | 
					    "node_modules/proxy-from-env": {
 | 
				
			||||||
      "version": "1.1.0",
 | 
					      "version": "1.1.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
 | 
				
			||||||
@ -7673,6 +7731,19 @@
 | 
				
			|||||||
      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
 | 
				
			||||||
      "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
 | 
					      "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/uuid": {
 | 
				
			||||||
 | 
					      "version": "9.0.1",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
 | 
				
			||||||
 | 
					      "funding": [
 | 
				
			||||||
 | 
					        "https://github.com/sponsors/broofa",
 | 
				
			||||||
 | 
					        "https://github.com/sponsors/ctavan"
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "peer": true,
 | 
				
			||||||
 | 
					      "bin": {
 | 
				
			||||||
 | 
					        "uuid": "dist/bin/uuid"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/v8-to-istanbul": {
 | 
					    "node_modules/v8-to-istanbul": {
 | 
				
			||||||
      "version": "9.2.0",
 | 
					      "version": "9.2.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz",
 | 
				
			||||||
@ -7748,6 +7819,57 @@
 | 
				
			|||||||
        "global": "^4.3.1"
 | 
					        "global": "^4.3.1"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/vis-data": {
 | 
				
			||||||
 | 
					      "version": "7.1.9",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/vis-data/-/vis-data-7.1.9.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-COQsxlVrmcRIbZMMTYwD+C2bxYCFDNQ2EHESklPiInbD/Pk3JZ6qNL84Bp9wWjYjAzXfSlsNaFtRk+hO9yBPWA==",
 | 
				
			||||||
 | 
					      "peer": true,
 | 
				
			||||||
 | 
					      "funding": {
 | 
				
			||||||
 | 
					        "type": "opencollective",
 | 
				
			||||||
 | 
					        "url": "https://opencollective.com/visjs"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "peerDependencies": {
 | 
				
			||||||
 | 
					        "uuid": "^3.4.0 || ^7.0.0 || ^8.0.0 || ^9.0.0",
 | 
				
			||||||
 | 
					        "vis-util": "^5.0.1"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/vis-timeline": {
 | 
				
			||||||
 | 
					      "version": "7.7.3",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/vis-timeline/-/vis-timeline-7.7.3.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-hGMzTttdOFWaw1PPlJuCXU2/4UjnsIxT684Thg9fV6YU1JuKZJs3s3BrJgZ4hO3gu5i1hsMe1YIi96o+eNT0jg==",
 | 
				
			||||||
 | 
					      "funding": {
 | 
				
			||||||
 | 
					        "type": "opencollective",
 | 
				
			||||||
 | 
					        "url": "https://opencollective.com/visjs"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "peerDependencies": {
 | 
				
			||||||
 | 
					        "@egjs/hammerjs": "^2.0.0",
 | 
				
			||||||
 | 
					        "component-emitter": "^1.3.0",
 | 
				
			||||||
 | 
					        "keycharm": "^0.2.0 || ^0.3.0 || ^0.4.0",
 | 
				
			||||||
 | 
					        "moment": "^2.24.0",
 | 
				
			||||||
 | 
					        "propagating-hammerjs": "^1.4.0 || ^2.0.0",
 | 
				
			||||||
 | 
					        "uuid": "^3.4.0 || ^7.0.0 || ^8.0.0 || ^9.0.0",
 | 
				
			||||||
 | 
					        "vis-data": "^6.3.0 || ^7.0.0",
 | 
				
			||||||
 | 
					        "vis-util": "^5.0.1",
 | 
				
			||||||
 | 
					        "xss": "^1.0.0"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/vis-util": {
 | 
				
			||||||
 | 
					      "version": "5.0.7",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/vis-util/-/vis-util-5.0.7.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-E3L03G3+trvc/X4LXvBfih3YIHcKS2WrP0XTdZefr6W6Qi/2nNCqZfe4JFfJU6DcQLm6Gxqj2Pfl+02859oL5A==",
 | 
				
			||||||
 | 
					      "peer": true,
 | 
				
			||||||
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "node": ">=8"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "funding": {
 | 
				
			||||||
 | 
					        "type": "opencollective",
 | 
				
			||||||
 | 
					        "url": "https://opencollective.com/visjs"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "peerDependencies": {
 | 
				
			||||||
 | 
					        "@egjs/hammerjs": "^2.0.0",
 | 
				
			||||||
 | 
					        "component-emitter": "^1.3.0 || ^2.0.0"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/vite": {
 | 
					    "node_modules/vite": {
 | 
				
			||||||
      "version": "5.0.5",
 | 
					      "version": "5.0.5",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.5.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.5.tgz",
 | 
				
			||||||
@ -8093,6 +8215,28 @@
 | 
				
			|||||||
      "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
 | 
					      "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
 | 
				
			||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/xss": {
 | 
				
			||||||
 | 
					      "version": "1.0.14",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.14.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-og7TEJhXvn1a7kzZGQ7ETjdQVS2UfZyTlsEdDOqvQF7GoxNfY+0YLCzBy1kPdsDDx4QuNAonQPddpsn6Xl/7sw==",
 | 
				
			||||||
 | 
					      "peer": true,
 | 
				
			||||||
 | 
					      "dependencies": {
 | 
				
			||||||
 | 
					        "commander": "^2.20.3",
 | 
				
			||||||
 | 
					        "cssfilter": "0.0.10"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "bin": {
 | 
				
			||||||
 | 
					        "xss": "bin/xss"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "node": ">= 0.10.0"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/xss/node_modules/commander": {
 | 
				
			||||||
 | 
					      "version": "2.20.3",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
 | 
				
			||||||
 | 
					      "peer": true
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/y18n": {
 | 
					    "node_modules/y18n": {
 | 
				
			||||||
      "version": "5.0.8",
 | 
					      "version": "5.0.8",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
 | 
				
			||||||
 | 
				
			|||||||
@ -53,6 +53,7 @@
 | 
				
			|||||||
    "tailwindcss-animate": "^1.0.7",
 | 
					    "tailwindcss-animate": "^1.0.7",
 | 
				
			||||||
    "video.js": "^8.6.1",
 | 
					    "video.js": "^8.6.1",
 | 
				
			||||||
    "videojs-playlist": "^5.1.0",
 | 
					    "videojs-playlist": "^5.1.0",
 | 
				
			||||||
 | 
					    "vis-timeline": "^7.7.3",
 | 
				
			||||||
    "vite-plugin-monaco-editor": "^1.1.0",
 | 
					    "vite-plugin-monaco-editor": "^1.1.0",
 | 
				
			||||||
    "zod": "^3.22.4"
 | 
					    "zod": "^3.22.4"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
				
			|||||||
@ -14,6 +14,7 @@ import ConfigEditor from "@/pages/ConfigEditor";
 | 
				
			|||||||
import Logs from "@/pages/Logs";
 | 
					import Logs from "@/pages/Logs";
 | 
				
			||||||
import NoMatch from "@/pages/NoMatch";
 | 
					import NoMatch from "@/pages/NoMatch";
 | 
				
			||||||
import Settings from "@/pages/Settings";
 | 
					import Settings from "@/pages/Settings";
 | 
				
			||||||
 | 
					import UIPlayground from "./pages/UIPlayground";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function App() {
 | 
					function App() {
 | 
				
			||||||
  const [sheetOpen, setSheetOpen] = useState(false);
 | 
					  const [sheetOpen, setSheetOpen] = useState(false);
 | 
				
			||||||
@ -29,7 +30,10 @@ function App() {
 | 
				
			|||||||
          <Header onToggleNavbar={toggleNavbar} />
 | 
					          <Header onToggleNavbar={toggleNavbar} />
 | 
				
			||||||
          <div className="grid grid-cols-[auto,1fr] flex-grow-1 overflow-auto">
 | 
					          <div className="grid grid-cols-[auto,1fr] flex-grow-1 overflow-auto">
 | 
				
			||||||
            <Sidebar sheetOpen={sheetOpen} setSheetOpen={setSheetOpen} />
 | 
					            <Sidebar sheetOpen={sheetOpen} setSheetOpen={setSheetOpen} />
 | 
				
			||||||
            <div id="pageRoot" className="overflow-x-hidden px-4 py-2 w-screen md:w-full">
 | 
					            <div
 | 
				
			||||||
 | 
					              id="pageRoot"
 | 
				
			||||||
 | 
					              className="overflow-x-hidden px-4 py-2 w-screen md:w-full"
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
              <Routes>
 | 
					              <Routes>
 | 
				
			||||||
                <Route path="/" element={<Dashboard />} />
 | 
					                <Route path="/" element={<Dashboard />} />
 | 
				
			||||||
                <Route path="/live/:camera?" element={<Live />} />
 | 
					                <Route path="/live/:camera?" element={<Live />} />
 | 
				
			||||||
@ -40,6 +44,7 @@ function App() {
 | 
				
			|||||||
                <Route path="/settings" element={<Settings />} />
 | 
					                <Route path="/settings" element={<Settings />} />
 | 
				
			||||||
                <Route path="/config" element={<ConfigEditor />} />
 | 
					                <Route path="/config" element={<ConfigEditor />} />
 | 
				
			||||||
                <Route path="/logs" element={<Logs />} />
 | 
					                <Route path="/logs" element={<Logs />} />
 | 
				
			||||||
 | 
					                <Route path="/playground" element={<UIPlayground />} />
 | 
				
			||||||
                <Route path="*" element={<NoMatch />} />
 | 
					                <Route path="*" element={<NoMatch />} />
 | 
				
			||||||
              </Routes>
 | 
					              </Routes>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 | 
				
			|||||||
@ -119,7 +119,7 @@ function Header({ onToggleNavbar }: HeaderProps) {
 | 
				
			|||||||
              <LuMoreVertical />
 | 
					              <LuMoreVertical />
 | 
				
			||||||
            </Button>
 | 
					            </Button>
 | 
				
			||||||
          </DropdownMenuTrigger>
 | 
					          </DropdownMenuTrigger>
 | 
				
			||||||
          <DropdownMenuContent className="w-72 mr-5">
 | 
					          <DropdownMenuContent className="md:w-72 mr-5">
 | 
				
			||||||
            <DropdownMenuLabel>System</DropdownMenuLabel>
 | 
					            <DropdownMenuLabel>System</DropdownMenuLabel>
 | 
				
			||||||
            <DropdownMenuSeparator />
 | 
					            <DropdownMenuSeparator />
 | 
				
			||||||
            <DropdownMenuGroup>
 | 
					            <DropdownMenuGroup>
 | 
				
			||||||
 | 
				
			|||||||
@ -1,8 +1,15 @@
 | 
				
			|||||||
import { IconType } from "react-icons";
 | 
					import { IconType } from "react-icons";
 | 
				
			||||||
import { LuFileUp, LuFilm, LuLayoutDashboard, LuVideo } from "react-icons/lu";
 | 
					import {
 | 
				
			||||||
 | 
					  LuConstruction,
 | 
				
			||||||
 | 
					  LuFileUp,
 | 
				
			||||||
 | 
					  LuFilm,
 | 
				
			||||||
 | 
					  LuLayoutDashboard,
 | 
				
			||||||
 | 
					  LuVideo,
 | 
				
			||||||
 | 
					} from "react-icons/lu";
 | 
				
			||||||
import { NavLink } from "react-router-dom";
 | 
					import { NavLink } from "react-router-dom";
 | 
				
			||||||
import { Sheet, SheetContent } from "@/components/ui/sheet";
 | 
					import { Sheet, SheetContent } from "@/components/ui/sheet";
 | 
				
			||||||
import Logo from "./Logo";
 | 
					import Logo from "./Logo";
 | 
				
			||||||
 | 
					import { ENV } from "@/env";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const navbarLinks = [
 | 
					const navbarLinks = [
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
@ -29,6 +36,13 @@ const navbarLinks = [
 | 
				
			|||||||
    title: "Export",
 | 
					    title: "Export",
 | 
				
			||||||
    url: "/export",
 | 
					    url: "/export",
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    id: 5,
 | 
				
			||||||
 | 
					    icon: LuConstruction,
 | 
				
			||||||
 | 
					    title: "UI Playground",
 | 
				
			||||||
 | 
					    url: "/playground",
 | 
				
			||||||
 | 
					    dev: true,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function Sidebar({
 | 
					function Sidebar({
 | 
				
			||||||
@ -46,6 +60,7 @@ function Sidebar({
 | 
				
			|||||||
          Icon={item.icon}
 | 
					          Icon={item.icon}
 | 
				
			||||||
          title={item.title}
 | 
					          title={item.title}
 | 
				
			||||||
          url={item.url}
 | 
					          url={item.url}
 | 
				
			||||||
 | 
					          dev={item.dev}
 | 
				
			||||||
          onClick={() => setSheetOpen(false)}
 | 
					          onClick={() => setSheetOpen(false)}
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
      ))}
 | 
					      ))}
 | 
				
			||||||
@ -78,11 +93,15 @@ type SidebarItemProps = {
 | 
				
			|||||||
  Icon: IconType;
 | 
					  Icon: IconType;
 | 
				
			||||||
  title: string;
 | 
					  title: string;
 | 
				
			||||||
  url: string;
 | 
					  url: string;
 | 
				
			||||||
 | 
					  dev?: boolean;
 | 
				
			||||||
  onClick?: () => void;
 | 
					  onClick?: () => void;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function SidebarItem({ Icon, title, url, onClick }: SidebarItemProps) {
 | 
					function SidebarItem({ Icon, title, url, dev, onClick }: SidebarItemProps) {
 | 
				
			||||||
 | 
					  const shouldRender = dev ? ENV !== "production" : true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
 | 
					    shouldRender && (
 | 
				
			||||||
      <NavLink
 | 
					      <NavLink
 | 
				
			||||||
        to={url}
 | 
					        to={url}
 | 
				
			||||||
        onClick={onClick}
 | 
					        onClick={onClick}
 | 
				
			||||||
@ -93,8 +112,9 @@ function SidebarItem({ Icon, title, url, onClick }: SidebarItemProps) {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        <Icon className="w-6 h-6 mr-1" />
 | 
					        <Icon className="w-6 h-6 mr-1" />
 | 
				
			||||||
      <div className="text-sm">{title}</div>
 | 
					        <div className="text-sm text-center">{title}</div>
 | 
				
			||||||
      </NavLink>
 | 
					      </NavLink>
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										46
									
								
								web/src/components/playground/TimelineScrubber.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								web/src/components/playground/TimelineScrubber.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,46 @@
 | 
				
			|||||||
 | 
					import useSWR from "swr";
 | 
				
			||||||
 | 
					import ActivityScrubber, { ScrubberItem } from "../scrubber/ActivityScrubber";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type TimelineScrubberProps = {
 | 
				
			||||||
 | 
					  eventID: string;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function timelineEventsToScrubberItems(events: Timeline[]): ScrubberItem[] {
 | 
				
			||||||
 | 
					  return events.map((event: Timeline, index: number) => ({
 | 
				
			||||||
 | 
					    id: index,
 | 
				
			||||||
 | 
					    content: event.class_type,
 | 
				
			||||||
 | 
					    start: event.timestamp * 1000,
 | 
				
			||||||
 | 
					    type: "box",
 | 
				
			||||||
 | 
					  }));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function generateScrubberOptions(events: Timeline[]) {
 | 
				
			||||||
 | 
					  const startTime = events[0].timestamp * 1000 - 10;
 | 
				
			||||||
 | 
					  const endTime = events[events.length - 1].timestamp * 1000 + 10;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return { start: startTime, end: endTime };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function TimelineScrubber({ eventID }: TimelineScrubberProps) {
 | 
				
			||||||
 | 
					  const { data: eventTimeline } = useSWR<Timeline[]>([
 | 
				
			||||||
 | 
					    "timeline",
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      source_id: eventID,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <>
 | 
				
			||||||
 | 
					      {eventTimeline && (
 | 
				
			||||||
 | 
					        <>
 | 
				
			||||||
 | 
					          <ActivityScrubber
 | 
				
			||||||
 | 
					            items={timelineEventsToScrubberItems(eventTimeline)}
 | 
				
			||||||
 | 
					            options={generateScrubberOptions(eventTimeline)}
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					        </>
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default TimelineScrubber;
 | 
				
			||||||
							
								
								
									
										180
									
								
								web/src/components/scrubber/ActivityScrubber.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										180
									
								
								web/src/components/scrubber/ActivityScrubber.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,180 @@
 | 
				
			|||||||
 | 
					import { useEffect, useRef, useState } from "react";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  Timeline as VisTimeline,
 | 
				
			||||||
 | 
					  TimelineGroup,
 | 
				
			||||||
 | 
					  TimelineItem,
 | 
				
			||||||
 | 
					  TimelineOptions,
 | 
				
			||||||
 | 
					} from "vis-timeline";
 | 
				
			||||||
 | 
					import type { DataGroup, DataItem, TimelineEvents } from "vis-timeline/types";
 | 
				
			||||||
 | 
					import "./scrubber.css";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type TimelineEventsWithMissing =
 | 
				
			||||||
 | 
					  | TimelineEvents
 | 
				
			||||||
 | 
					  | "dragover"
 | 
				
			||||||
 | 
					  | "markerchange"
 | 
				
			||||||
 | 
					  | "markerchanged";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type TimelineEventHandler =
 | 
				
			||||||
 | 
					  | "currentTimeTickHandler"
 | 
				
			||||||
 | 
					  | "clickHandler"
 | 
				
			||||||
 | 
					  | "contextmenuHandler"
 | 
				
			||||||
 | 
					  | "doubleClickHandler"
 | 
				
			||||||
 | 
					  | "dragoverHandler"
 | 
				
			||||||
 | 
					  | "dropHandler"
 | 
				
			||||||
 | 
					  | "mouseOverHandler"
 | 
				
			||||||
 | 
					  | "mouseDownHandler"
 | 
				
			||||||
 | 
					  | "mouseUpHandler"
 | 
				
			||||||
 | 
					  | "mouseMoveHandler"
 | 
				
			||||||
 | 
					  | "groupDraggedHandler"
 | 
				
			||||||
 | 
					  | "changedHandler"
 | 
				
			||||||
 | 
					  | "rangechangeHandler"
 | 
				
			||||||
 | 
					  | "rangechangedHandler"
 | 
				
			||||||
 | 
					  | "selectHandler"
 | 
				
			||||||
 | 
					  | "itemoverHandler"
 | 
				
			||||||
 | 
					  | "itemoutHandler"
 | 
				
			||||||
 | 
					  | "timechangeHandler"
 | 
				
			||||||
 | 
					  | "timechangedHandler"
 | 
				
			||||||
 | 
					  | "markerchangeHandler"
 | 
				
			||||||
 | 
					  | "markerchangedHandler";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type EventHandler = {
 | 
				
			||||||
 | 
					  (properties: any): void;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type TimelineEventsHandlers = Partial<
 | 
				
			||||||
 | 
					  Record<TimelineEventHandler, EventHandler>
 | 
				
			||||||
 | 
					>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type ScrubberItem = TimelineItem;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const domEvents: TimelineEventsWithMissing[] = [
 | 
				
			||||||
 | 
					  "currentTimeTick",
 | 
				
			||||||
 | 
					  "click",
 | 
				
			||||||
 | 
					  "contextmenu",
 | 
				
			||||||
 | 
					  "doubleClick",
 | 
				
			||||||
 | 
					  "dragover",
 | 
				
			||||||
 | 
					  "drop",
 | 
				
			||||||
 | 
					  "mouseOver",
 | 
				
			||||||
 | 
					  "mouseDown",
 | 
				
			||||||
 | 
					  "mouseUp",
 | 
				
			||||||
 | 
					  "mouseMove",
 | 
				
			||||||
 | 
					  "groupDragged",
 | 
				
			||||||
 | 
					  "changed",
 | 
				
			||||||
 | 
					  "rangechange",
 | 
				
			||||||
 | 
					  "rangechanged",
 | 
				
			||||||
 | 
					  "select",
 | 
				
			||||||
 | 
					  "itemover",
 | 
				
			||||||
 | 
					  "itemout",
 | 
				
			||||||
 | 
					  "timechange",
 | 
				
			||||||
 | 
					  "timechanged",
 | 
				
			||||||
 | 
					  "markerchange",
 | 
				
			||||||
 | 
					  "markerchanged",
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ActivityScrubberProps = {
 | 
				
			||||||
 | 
					  items: TimelineItem[];
 | 
				
			||||||
 | 
					  groups?: TimelineGroup[];
 | 
				
			||||||
 | 
					  options?: TimelineOptions;
 | 
				
			||||||
 | 
					} & TimelineEventsHandlers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function ActivityScrubber({
 | 
				
			||||||
 | 
					  items,
 | 
				
			||||||
 | 
					  groups,
 | 
				
			||||||
 | 
					  options,
 | 
				
			||||||
 | 
					  ...eventHandlers
 | 
				
			||||||
 | 
					}: ActivityScrubberProps) {
 | 
				
			||||||
 | 
					  const containerRef = useRef<HTMLDivElement>(null);
 | 
				
			||||||
 | 
					  const timelineRef = useRef<{ timeline: VisTimeline | null }>({
 | 
				
			||||||
 | 
					    timeline: null,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  const [currentTime, setCurrentTime] = useState(Date.now());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const defaultOptions: TimelineOptions = {
 | 
				
			||||||
 | 
					    width: "100%",
 | 
				
			||||||
 | 
					    maxHeight: "350px",
 | 
				
			||||||
 | 
					    stack: true,
 | 
				
			||||||
 | 
					    showMajorLabels: true,
 | 
				
			||||||
 | 
					    showCurrentTime: false,
 | 
				
			||||||
 | 
					    zoomMin: 10 * 1000, // 10 seconds
 | 
				
			||||||
 | 
					    // start: new Date(currentTime - 60 * 1 * 60 * 1000), // 1 hour ago
 | 
				
			||||||
 | 
					    end: currentTime,
 | 
				
			||||||
 | 
					    max: currentTime,
 | 
				
			||||||
 | 
					    format: {
 | 
				
			||||||
 | 
					      minorLabels: {
 | 
				
			||||||
 | 
					        minute: "h:mma",
 | 
				
			||||||
 | 
					        hour: "ha",
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    const intervalId = setInterval(() => {
 | 
				
			||||||
 | 
					      setCurrentTime(Date.now());
 | 
				
			||||||
 | 
					    }, 60000); // Update every minute
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return () => {
 | 
				
			||||||
 | 
					      clearInterval(intervalId);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    const divElement = containerRef.current;
 | 
				
			||||||
 | 
					    if (!divElement) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const timelineInstance = new VisTimeline(
 | 
				
			||||||
 | 
					      divElement,
 | 
				
			||||||
 | 
					      items as DataItem[],
 | 
				
			||||||
 | 
					      groups as DataGroup[],
 | 
				
			||||||
 | 
					      options
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    domEvents.forEach((event) => {
 | 
				
			||||||
 | 
					      const eventHandler = eventHandlers[`${event}Handler`];
 | 
				
			||||||
 | 
					      if (typeof eventHandler === "function") {
 | 
				
			||||||
 | 
					        timelineInstance.on(event, eventHandler);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    timelineRef.current.timeline = timelineInstance;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const timelineOptions: TimelineOptions = {
 | 
				
			||||||
 | 
					      ...defaultOptions,
 | 
				
			||||||
 | 
					      ...options,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    timelineInstance.setOptions(timelineOptions);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return () => {
 | 
				
			||||||
 | 
					      timelineInstance.destroy();
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    if (!timelineRef.current.timeline) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // If the currentTime updates, adjust the scrubber's end date and max
 | 
				
			||||||
 | 
					    // May not be applicable to all scrubbers, might want to just pass this in
 | 
				
			||||||
 | 
					    // for any scrubbers that we want to dynamically move based on time
 | 
				
			||||||
 | 
					    // const updatedTimeOptions: TimelineOptions = {
 | 
				
			||||||
 | 
					    //   end: currentTime,
 | 
				
			||||||
 | 
					    //   max: currentTime,
 | 
				
			||||||
 | 
					    // };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const timelineOptions: TimelineOptions = {
 | 
				
			||||||
 | 
					      ...defaultOptions,
 | 
				
			||||||
 | 
					      // ...updatedTimeOptions,
 | 
				
			||||||
 | 
					      ...options,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    timelineRef.current.timeline.setOptions(timelineOptions);
 | 
				
			||||||
 | 
					    if (items) timelineRef.current.timeline.setItems(items);
 | 
				
			||||||
 | 
					  }, [items, groups, options, currentTime, eventHandlers]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return <div ref={containerRef} />;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default ActivityScrubber;
 | 
				
			||||||
							
								
								
									
										670
									
								
								web/src/components/scrubber/scrubber.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										670
									
								
								web/src/components/scrubber/scrubber.css
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,670 @@
 | 
				
			|||||||
 | 
					.vis-time-axis {
 | 
				
			||||||
 | 
					  @apply overflow-hidden relative;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-time-axis.vis-foreground {
 | 
				
			||||||
 | 
					  @apply w-full left-0 top-0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-time-axis.vis-background {
 | 
				
			||||||
 | 
					  @apply h-full absolute w-full left-0 top-0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-time-axis .vis-text {
 | 
				
			||||||
 | 
					  @apply box-border text-muted-foreground overflow-hidden absolute whitespace-nowrap p-[3px];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-time-axis .vis-text.vis-measure {
 | 
				
			||||||
 | 
					  @apply absolute invisible mx-0 px-0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-time-axis .vis-grid.vis-vertical {
 | 
				
			||||||
 | 
					  @apply absolute border-l border-dashed border-muted-foreground;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-time-axis .vis-grid.vis-vertical-rtl {
 | 
				
			||||||
 | 
					  @apply absolute border-r border-dashed border-muted-foreground;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-time-axis .vis-grid.vis-minor {
 | 
				
			||||||
 | 
					  @apply border-foreground;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-time-axis .vis-grid.vis-major {
 | 
				
			||||||
 | 
					  @apply border-muted-foreground;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis .overlay {
 | 
				
			||||||
 | 
					  @apply h-full absolute w-full z-10 left-0 top-0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-active {
 | 
				
			||||||
 | 
					  @apply shadow-[0_0_10px_#86d5f8];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-custom-time {
 | 
				
			||||||
 | 
					  @apply bg-[#6e94ff] cursor-move w-0.5 z-[1];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-custom-time > .vis-custom-time-marker {
 | 
				
			||||||
 | 
					  @apply bg-inherit text-white cursor-auto text-xs whitespace-nowrap z-[inherit] px-[5px] py-[3px] top-0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-current-time {
 | 
				
			||||||
 | 
					  @apply bg-[#ff7f6e] pointer-events-none w-0.5 z-[1];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-rolling-mode-btn {
 | 
				
			||||||
 | 
					  @apply text-white cursor-pointer text-[28px] font-bold h-10 opacity-80 absolute text-center w-10 rounded-[50%] right-5 top-[7px] before:content-["\26F6"] hover:opacity-100;
 | 
				
			||||||
 | 
					  background: #3876c2;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-panel {
 | 
				
			||||||
 | 
					  @apply box-border absolute m-0 p-0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-panel.vis-bottom,
 | 
				
			||||||
 | 
					.vis-panel.vis-center,
 | 
				
			||||||
 | 
					.vis-panel.vis-left,
 | 
				
			||||||
 | 
					.vis-panel.vis-right,
 | 
				
			||||||
 | 
					.vis-panel.vis-top {
 | 
				
			||||||
 | 
					  @apply border;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-panel.vis-center,
 | 
				
			||||||
 | 
					.vis-panel.vis-left,
 | 
				
			||||||
 | 
					.vis-panel.vis-right {
 | 
				
			||||||
 | 
					  @apply overflow-hidden;
 | 
				
			||||||
 | 
					  border-bottom-style: solid;
 | 
				
			||||||
 | 
					  border-top-style: solid;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-left.vis-panel.vis-vertical-scroll,
 | 
				
			||||||
 | 
					.vis-right.vis-panel.vis-vertical-scroll {
 | 
				
			||||||
 | 
					  @apply h-full overflow-x-hidden overflow-y-scroll;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-left.vis-panel.vis-vertical-scroll {
 | 
				
			||||||
 | 
					  direction: rtl;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-left.vis-panel.vis-vertical-scroll .vis-content,
 | 
				
			||||||
 | 
					.vis-right.vis-panel.vis-vertical-scroll {
 | 
				
			||||||
 | 
					  direction: ltr;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-right.vis-panel.vis-vertical-scroll .vis-content {
 | 
				
			||||||
 | 
					  direction: rtl;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-panel.vis-bottom,
 | 
				
			||||||
 | 
					.vis-panel.vis-center,
 | 
				
			||||||
 | 
					.vis-panel.vis-top {
 | 
				
			||||||
 | 
					  border-left-style: solid;
 | 
				
			||||||
 | 
					  border-right-style: solid;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-background {
 | 
				
			||||||
 | 
					  @apply overflow-hidden;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-panel > .vis-content {
 | 
				
			||||||
 | 
					  @apply relative;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-panel .vis-shadow {
 | 
				
			||||||
 | 
					  @apply shadow-[0_0_10px_rgba(0,0,0,0.8)] h-px absolute w-full;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-panel .vis-shadow.vis-top {
 | 
				
			||||||
 | 
					  @apply left-0 -top-px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-panel .vis-shadow.vis-bottom {
 | 
				
			||||||
 | 
					  @apply left-0 -bottom-px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-graph-group0 {
 | 
				
			||||||
 | 
					  @apply fill-[#4f81bd] stroke-[2px] stroke-[#4f81bd];
 | 
				
			||||||
 | 
					  fill-opacity: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-graph-group1 {
 | 
				
			||||||
 | 
					  @apply fill-[#f79646] stroke-[2px] stroke-[#f79646];
 | 
				
			||||||
 | 
					  fill-opacity: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-graph-group2 {
 | 
				
			||||||
 | 
					  @apply fill-[#8c51cf] stroke-[2px] stroke-[#8c51cf];
 | 
				
			||||||
 | 
					  fill-opacity: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-graph-group3 {
 | 
				
			||||||
 | 
					  @apply fill-[#75c841] stroke-[2px] stroke-[#75c841];
 | 
				
			||||||
 | 
					  fill-opacity: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-graph-group4 {
 | 
				
			||||||
 | 
					  @apply fill-[#ff0100] stroke-[2px] stroke-[#ff0100];
 | 
				
			||||||
 | 
					  fill-opacity: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-graph-group5 {
 | 
				
			||||||
 | 
					  @apply fill-[#37d8e6] stroke-[2px] stroke-[#37d8e6];
 | 
				
			||||||
 | 
					  fill-opacity: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-graph-group6 {
 | 
				
			||||||
 | 
					  @apply fill-[#042662] stroke-[2px] stroke-[#042662];
 | 
				
			||||||
 | 
					  fill-opacity: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-graph-group7 {
 | 
				
			||||||
 | 
					  @apply fill-[#00ff26] stroke-[2px] stroke-[#00ff26];
 | 
				
			||||||
 | 
					  fill-opacity: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-graph-group8 {
 | 
				
			||||||
 | 
					  @apply fill-[#f0f] stroke-[2px] stroke-[#f0f];
 | 
				
			||||||
 | 
					  fill-opacity: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-graph-group9 {
 | 
				
			||||||
 | 
					  @apply fill-[#8f3938] stroke-[2px] stroke-[#8f3938];
 | 
				
			||||||
 | 
					  fill-opacity: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-timeline .vis-fill {
 | 
				
			||||||
 | 
					  @apply stroke-none;
 | 
				
			||||||
 | 
					  fill-opacity: 0.1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-timeline .vis-bar {
 | 
				
			||||||
 | 
					  @apply stroke-[1px];
 | 
				
			||||||
 | 
					  fill-opacity: 0.5;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-timeline .vis-point {
 | 
				
			||||||
 | 
					  @apply stroke-[2px];
 | 
				
			||||||
 | 
					  fill-opacity: 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-timeline .vis-legend-background {
 | 
				
			||||||
 | 
					  @apply stroke-[1px] fill-white stroke-[#c2c2c2];
 | 
				
			||||||
 | 
					  fill-opacity: 0.9;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-timeline .vis-outline {
 | 
				
			||||||
 | 
					  @apply stroke-[1px] fill-white stroke-neutral-200;
 | 
				
			||||||
 | 
					  fill-opacity: 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-timeline .vis-icon-fill {
 | 
				
			||||||
 | 
					  @apply stroke-none;
 | 
				
			||||||
 | 
					  fill-opacity: 0.3;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-timeline {
 | 
				
			||||||
 | 
					  @apply border box-border overflow-hidden relative m-0 p-0 border-solid border-[#bfbfbf];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-loading-screen {
 | 
				
			||||||
 | 
					  @apply h-full absolute w-full left-0 top-0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis [class*="span"] {
 | 
				
			||||||
 | 
					  @apply min-h-0 w-auto;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-item {
 | 
				
			||||||
 | 
					  @apply bg-accent border text-foreground inline-block absolute z-[1] border-border;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-item.vis-selected {
 | 
				
			||||||
 | 
					  @apply bg-muted-foreground z-[2] border-muted text-muted;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-editable.vis-selected {
 | 
				
			||||||
 | 
					  @apply cursor-move;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-item.vis-point.vis-selected {
 | 
				
			||||||
 | 
					  @apply bg-muted-foreground;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-item.vis-box {
 | 
				
			||||||
 | 
					  @apply text-center rounded-sm border-solid;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-item.vis-point {
 | 
				
			||||||
 | 
					  background: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-item.vis-dot {
 | 
				
			||||||
 | 
					  @apply rounded absolute p-0 border-solid border-4;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-item.vis-range {
 | 
				
			||||||
 | 
					  @apply box-border rounded-sm border-solid;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-item.vis-background {
 | 
				
			||||||
 | 
					  @apply bg-[rgba(213,221,246,0.4)] box-border m-0 p-0 border-[none];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-item .vis-item-overflow {
 | 
				
			||||||
 | 
					  @apply h-full overflow-hidden relative w-full m-0 p-0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-item-visible-frame {
 | 
				
			||||||
 | 
					  @apply whitespace-nowrap;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-item.vis-range .vis-item-content {
 | 
				
			||||||
 | 
					  @apply inline-block relative;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-item.vis-background .vis-item-content {
 | 
				
			||||||
 | 
					  @apply inline-block absolute;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-item.vis-line {
 | 
				
			||||||
 | 
					  @apply absolute w-0 p-0 border-l border-solid text-muted;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-item .vis-item-content {
 | 
				
			||||||
 | 
					  @apply box-border whitespace-nowrap p-[5px];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-item .vis-onUpdateTime-tooltip {
 | 
				
			||||||
 | 
					  @apply text-white absolute text-center transition-[0.4s] whitespace-nowrap w-[200px] p-[5px] rounded-[1px];
 | 
				
			||||||
 | 
					  background: #4f81bd;
 | 
				
			||||||
 | 
					  -o-transition: 0.4s;
 | 
				
			||||||
 | 
					  -moz-transition: 0.4s;
 | 
				
			||||||
 | 
					  -webkit-transition: 0.4s;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-item .vis-delete,
 | 
				
			||||||
 | 
					.vis-item .vis-delete-rtl {
 | 
				
			||||||
 | 
					  @apply box-border cursor-pointer h-6 absolute transition-[background] duration-200 ease-linear w-6 px-[5px] py-0 top-0;
 | 
				
			||||||
 | 
					  -webkit-transition: background 0.2s linear;
 | 
				
			||||||
 | 
					  -moz-transition: background 0.2s linear;
 | 
				
			||||||
 | 
					  -ms-transition: background 0.2s linear;
 | 
				
			||||||
 | 
					  -o-transition: background 0.2s linear;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-item .vis-delete {
 | 
				
			||||||
 | 
					  @apply -right-6;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-item .vis-delete-rtl {
 | 
				
			||||||
 | 
					  @apply -left-6;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-item .vis-delete-rtl:after,
 | 
				
			||||||
 | 
					.vis-item .vis-delete:after {
 | 
				
			||||||
 | 
					  @apply text-[red] content-["\00D7"] text-[22px] font-bold transition-[color] duration-200 ease-linear;
 | 
				
			||||||
 | 
					  font-family: arial, sans-serif;
 | 
				
			||||||
 | 
					  -webkit-transition: color 0.2s linear;
 | 
				
			||||||
 | 
					  -moz-transition: color 0.2s linear;
 | 
				
			||||||
 | 
					  -ms-transition: color 0.2s linear;
 | 
				
			||||||
 | 
					  -o-transition: color 0.2s linear;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-item .vis-delete-rtl:hover,
 | 
				
			||||||
 | 
					.vis-item .vis-delete:hover {
 | 
				
			||||||
 | 
					  background: red;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-item .vis-delete-rtl:hover:after,
 | 
				
			||||||
 | 
					.vis-item .vis-delete:hover:after {
 | 
				
			||||||
 | 
					  @apply text-white;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-item .vis-drag-center {
 | 
				
			||||||
 | 
					  @apply cursor-move h-full absolute w-full left-0 top-0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-item.vis-range .vis-drag-left {
 | 
				
			||||||
 | 
					  @apply cursor-w-resize -left-1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-item.vis-range .vis-drag-left,
 | 
				
			||||||
 | 
					.vis-item.vis-range .vis-drag-right {
 | 
				
			||||||
 | 
					  @apply h-full max-w-[20%] min-w-[2px] absolute w-6 top-0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-item.vis-range .vis-drag-right {
 | 
				
			||||||
 | 
					  @apply cursor-e-resize -right-1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-range.vis-item.vis-readonly .vis-drag-left,
 | 
				
			||||||
 | 
					.vis-range.vis-item.vis-readonly .vis-drag-right {
 | 
				
			||||||
 | 
					  @apply cursor-auto;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-item.vis-cluster {
 | 
				
			||||||
 | 
					  @apply text-center rounded-sm border-solid;
 | 
				
			||||||
 | 
					  vertical-align: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-item.vis-cluster-line {
 | 
				
			||||||
 | 
					  @apply absolute w-0 p-0 border-l;
 | 
				
			||||||
 | 
					  border-left-style: solid;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-item.vis-cluster-dot {
 | 
				
			||||||
 | 
					  @apply rounded absolute p-0 border-solid border-4;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					div.vis-tooltip {
 | 
				
			||||||
 | 
					  @apply bg-[#f5f4ed] border shadow-[3px_3px_10px_rgba(0,0,0,0.2)] text-black text-sm pointer-events-none absolute invisible whitespace-nowrap z-[5] p-[5px] rounded-[3px] border-solid border-[#808074];
 | 
				
			||||||
 | 
					  -moz-border-radius: 3px;
 | 
				
			||||||
 | 
					  -webkit-border-radius: 3px;
 | 
				
			||||||
 | 
					  font-family: verdana;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-itemset {
 | 
				
			||||||
 | 
					  @apply box-border relative m-0 p-0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-itemset .vis-background,
 | 
				
			||||||
 | 
					.vis-itemset .vis-foreground {
 | 
				
			||||||
 | 
					  @apply h-full overflow-visible absolute w-full;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-axis {
 | 
				
			||||||
 | 
					  @apply h-0 absolute w-full z-[1] left-0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-foreground .vis-group {
 | 
				
			||||||
 | 
					  @apply box-border relative border-b-[#bfbfbf] border-b border-solid last:border-b-[none];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-nesting-group {
 | 
				
			||||||
 | 
					  @apply cursor-pointer;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-label.vis-nested-group.vis-group-level-unknown-but-gte1 {
 | 
				
			||||||
 | 
					  background: #f5f5f5;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-label.vis-nested-group.vis-group-level-0 {
 | 
				
			||||||
 | 
					  @apply bg-white;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-ltr .vis-label.vis-nested-group.vis-group-level-0 .vis-inner {
 | 
				
			||||||
 | 
					  @apply pl-0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-rtl .vis-label.vis-nested-group.vis-group-level-0 .vis-inner {
 | 
				
			||||||
 | 
					  @apply pr-0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-label.vis-nested-group.vis-group-level-1 {
 | 
				
			||||||
 | 
					  @apply bg-[rgba(0,0,0,0.05)];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-ltr .vis-label.vis-nested-group.vis-group-level-1 .vis-inner {
 | 
				
			||||||
 | 
					  @apply pl-[15px];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-rtl .vis-label.vis-nested-group.vis-group-level-1 .vis-inner {
 | 
				
			||||||
 | 
					  @apply pr-[15px];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-label.vis-nested-group.vis-group-level-2 {
 | 
				
			||||||
 | 
					  @apply bg-[rgba(0,0,0,0.1)];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-ltr .vis-label.vis-nested-group.vis-group-level-2 .vis-inner {
 | 
				
			||||||
 | 
					  @apply pl-[30px];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-rtl .vis-label.vis-nested-group.vis-group-level-2 .vis-inner {
 | 
				
			||||||
 | 
					  @apply pr-[30px];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-label.vis-nested-group.vis-group-level-3 {
 | 
				
			||||||
 | 
					  @apply bg-[rgba(0,0,0,0.15)];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-ltr .vis-label.vis-nested-group.vis-group-level-3 .vis-inner {
 | 
				
			||||||
 | 
					  @apply pl-[45px];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-rtl .vis-label.vis-nested-group.vis-group-level-3 .vis-inner {
 | 
				
			||||||
 | 
					  @apply pr-[45px];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-label.vis-nested-group.vis-group-level-4 {
 | 
				
			||||||
 | 
					  @apply bg-[rgba(0,0,0,0.2)];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-ltr .vis-label.vis-nested-group.vis-group-level-4 .vis-inner {
 | 
				
			||||||
 | 
					  @apply pl-[60px];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-rtl .vis-label.vis-nested-group.vis-group-level-4 .vis-inner {
 | 
				
			||||||
 | 
					  @apply pr-[60px];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-label.vis-nested-group.vis-group-level-5 {
 | 
				
			||||||
 | 
					  @apply bg-[rgba(0,0,0,0.25)];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-ltr .vis-label.vis-nested-group.vis-group-level-5 .vis-inner {
 | 
				
			||||||
 | 
					  @apply pl-[75px];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-rtl .vis-label.vis-nested-group.vis-group-level-5 .vis-inner {
 | 
				
			||||||
 | 
					  @apply pr-[75px];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-label.vis-nested-group.vis-group-level-6 {
 | 
				
			||||||
 | 
					  @apply bg-[rgba(0,0,0,0.3)];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-ltr .vis-label.vis-nested-group.vis-group-level-6 .vis-inner {
 | 
				
			||||||
 | 
					  @apply pl-[90px];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-rtl .vis-label.vis-nested-group.vis-group-level-6 .vis-inner {
 | 
				
			||||||
 | 
					  @apply pr-[90px];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-label.vis-nested-group.vis-group-level-7 {
 | 
				
			||||||
 | 
					  @apply bg-[rgba(0,0,0,0.35)];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-ltr .vis-label.vis-nested-group.vis-group-level-7 .vis-inner {
 | 
				
			||||||
 | 
					  @apply pl-[105px];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-rtl .vis-label.vis-nested-group.vis-group-level-7 .vis-inner {
 | 
				
			||||||
 | 
					  @apply pr-[105px];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-label.vis-nested-group.vis-group-level-8 {
 | 
				
			||||||
 | 
					  @apply bg-[rgba(0,0,0,0.4)];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-ltr .vis-label.vis-nested-group.vis-group-level-8 .vis-inner {
 | 
				
			||||||
 | 
					  @apply pl-[120px];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-rtl .vis-label.vis-nested-group.vis-group-level-8 .vis-inner {
 | 
				
			||||||
 | 
					  @apply pr-[120px];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-label.vis-nested-group.vis-group-level-9 {
 | 
				
			||||||
 | 
					  @apply bg-[rgba(0,0,0,0.45)];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-ltr .vis-label.vis-nested-group.vis-group-level-9 .vis-inner {
 | 
				
			||||||
 | 
					  @apply pl-[135px];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-rtl .vis-label.vis-nested-group.vis-group-level-9 .vis-inner {
 | 
				
			||||||
 | 
					  @apply pr-[135px];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-label.vis-nested-group {
 | 
				
			||||||
 | 
					  @apply bg-[rgba(0,0,0,0.5)];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-ltr .vis-label.vis-nested-group .vis-inner {
 | 
				
			||||||
 | 
					  @apply pl-[150px];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-rtl .vis-label.vis-nested-group .vis-inner {
 | 
				
			||||||
 | 
					  @apply pr-[150px];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-group-level-unknown-but-gte1 {
 | 
				
			||||||
 | 
					  @apply border border-solid border-[red];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-label.vis-nesting-group:before {
 | 
				
			||||||
 | 
					  @apply inline-block w-[15px];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-label.vis-nesting-group.expanded:before {
 | 
				
			||||||
 | 
					  @apply content-["\25BC"];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-label.vis-nesting-group.collapsed:before {
 | 
				
			||||||
 | 
					  @apply content-["\25B6"];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-rtl .vis-label.vis-nesting-group.collapsed:before {
 | 
				
			||||||
 | 
					  @apply content-["\25C0"];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-ltr .vis-label:not(.vis-nesting-group):not(.vis-group-level-0) {
 | 
				
			||||||
 | 
					  @apply pl-[15px];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-rtl .vis-label:not(.vis-nesting-group):not(.vis-group-level-0) {
 | 
				
			||||||
 | 
					  @apply pr-[15px];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-overlay {
 | 
				
			||||||
 | 
					  @apply h-full absolute w-full z-10 left-0 top-0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-labelset {
 | 
				
			||||||
 | 
					  @apply overflow-hidden;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-labelset,
 | 
				
			||||||
 | 
					.vis-labelset .vis-label {
 | 
				
			||||||
 | 
					  @apply box-border relative;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-labelset .vis-label {
 | 
				
			||||||
 | 
					  @apply text-[#4d4d4d] w-full border-b-[#bfbfbf] border-b border-solid left-0 top-0 last:border-b-[none];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-labelset .vis-label.draggable {
 | 
				
			||||||
 | 
					  @apply cursor-pointer;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-group-is-dragging {
 | 
				
			||||||
 | 
					  background: rgba(0, 0, 0, 0.1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-labelset .vis-label .vis-inner {
 | 
				
			||||||
 | 
					  @apply inline-block p-[5px];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-labelset .vis-label .vis-inner.vis-hidden {
 | 
				
			||||||
 | 
					  @apply p-0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					div.vis-configuration {
 | 
				
			||||||
 | 
					  @apply block float-left text-xs relative;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					div.vis-configuration-wrapper {
 | 
				
			||||||
 | 
					  @apply block w-[700px] after:clear-both after:content-[""] after:block;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					div.vis-configuration.vis-config-option-container {
 | 
				
			||||||
 | 
					  @apply bg-white rounded block w-[495px] mt-5 pl-[5px] border-2 border-solid border-[#f7f8fa] left-2.5;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					div.vis-configuration.vis-config-button {
 | 
				
			||||||
 | 
					  @apply bg-[#f7f8fa] rounded cursor-pointer block h-[25px] leading-[25px] align-middle w-[495px] mt-5 mb-[30px] pl-[5px] border-2 border-solid border-[#ceced0] left-2.5;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					div.vis-configuration.vis-config-button.hover {
 | 
				
			||||||
 | 
					  @apply bg-[#4588e6] text-white border-2 border-solid border-[#214373];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					div.vis-configuration.vis-config-item {
 | 
				
			||||||
 | 
					  @apply block float-left h-[25px] leading-[25px] align-middle w-[495px];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					div.vis-configuration.vis-config-item.vis-config-s2 {
 | 
				
			||||||
 | 
					  @apply bg-[#f7f8fa] pl-[5px] rounded-[3px] left-2.5;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					div.vis-configuration.vis-config-item.vis-config-s3 {
 | 
				
			||||||
 | 
					  @apply bg-[#e4e9f0] pl-[5px] rounded-[3px] left-5;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					div.vis-configuration.vis-config-item.vis-config-s4 {
 | 
				
			||||||
 | 
					  @apply bg-[#cfd8e6] pl-[5px] rounded-[3px] left-[30px];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					div.vis-configuration.vis-config-header {
 | 
				
			||||||
 | 
					  @apply text-lg font-bold;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					div.vis-configuration.vis-config-label {
 | 
				
			||||||
 | 
					  @apply h-[25px] leading-[25px] w-[120px];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					div.vis-configuration.vis-config-label.vis-config-s3 {
 | 
				
			||||||
 | 
					  @apply w-[110px];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					div.vis-configuration.vis-config-label.vis-config-s4 {
 | 
				
			||||||
 | 
					  @apply w-[100px];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					div.vis-configuration.vis-config-colorBlock {
 | 
				
			||||||
 | 
					  @apply border cursor-pointer h-[19px] w-[30px] m-0 p-0 rounded-sm border-solid border-[#444] top-px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					input.vis-configuration.vis-config-checkbox {
 | 
				
			||||||
 | 
					  @apply left-[-5px];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					input.vis-configuration.vis-config-rangeinput {
 | 
				
			||||||
 | 
					  @apply pointer-events-none relative top-[-5px] w-[60px] m-0 p-px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					input.vis-configuration.vis-config-range {
 | 
				
			||||||
 | 
					  @apply bg-transparent h-5 w-[300px] border-0 border-solid border-white;
 | 
				
			||||||
 | 
					  -webkit-appearance: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					input.vis-configuration.vis-config-range::-webkit-slider-runnable-track {
 | 
				
			||||||
 | 
					  @apply border shadow-[0_0_3px_0_#aaa] h-[5px] w-[300px] rounded-[3px] border-solid border-[#999];
 | 
				
			||||||
 | 
					  background: #dedede;
 | 
				
			||||||
 | 
					  background: -moz-linear-gradient(top, #dedede 0, #c8c8c8 99%);
 | 
				
			||||||
 | 
					  background: -webkit-gradient(
 | 
				
			||||||
 | 
					    linear,
 | 
				
			||||||
 | 
					    left top,
 | 
				
			||||||
 | 
					    left bottom,
 | 
				
			||||||
 | 
					    color-stop(0, #dedede),
 | 
				
			||||||
 | 
					    color-stop(99%, #c8c8c8)
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  background: -webkit-linear-gradient(top, #dedede, #c8c8c8 99%);
 | 
				
			||||||
 | 
					  background: -o-linear-gradient(top, #dedede 0, #c8c8c8 99%);
 | 
				
			||||||
 | 
					  background: -ms-linear-gradient(top, #dedede 0, #c8c8c8 99%);
 | 
				
			||||||
 | 
					  background: linear-gradient(180deg, #dedede 0, #c8c8c8 99%);
 | 
				
			||||||
 | 
					  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#dedede",endColorstr="#c8c8c8",GradientType=0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					input.vis-configuration.vis-config-range::-webkit-slider-thumb {
 | 
				
			||||||
 | 
					  @apply border shadow-[0_0_1px_0_#111927] h-[17px] mt-[-7px] w-[17px] rounded-[50%] border-solid border-[#14334b];
 | 
				
			||||||
 | 
					  -webkit-appearance: none;
 | 
				
			||||||
 | 
					  background: #3876c2;
 | 
				
			||||||
 | 
					  background: -moz-linear-gradient(top, #3876c2 0, #385380 100%);
 | 
				
			||||||
 | 
					  background: -webkit-gradient(
 | 
				
			||||||
 | 
					    linear,
 | 
				
			||||||
 | 
					    left top,
 | 
				
			||||||
 | 
					    left bottom,
 | 
				
			||||||
 | 
					    color-stop(0, #3876c2),
 | 
				
			||||||
 | 
					    color-stop(100%, #385380)
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  background: -webkit-linear-gradient(top, #3876c2, #385380);
 | 
				
			||||||
 | 
					  background: -o-linear-gradient(top, #3876c2 0, #385380 100%);
 | 
				
			||||||
 | 
					  background: -ms-linear-gradient(top, #3876c2 0, #385380 100%);
 | 
				
			||||||
 | 
					  background: linear-gradient(180deg, #3876c2 0, #385380);
 | 
				
			||||||
 | 
					  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#3876c2",endColorstr="#385380",GradientType=0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					input.vis-configuration.vis-config-range:focus {
 | 
				
			||||||
 | 
					  outline: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					input.vis-configuration.vis-config-range:focus::-webkit-slider-runnable-track {
 | 
				
			||||||
 | 
					  background: #9d9d9d;
 | 
				
			||||||
 | 
					  background: -moz-linear-gradient(top, #9d9d9d 0, #c8c8c8 99%);
 | 
				
			||||||
 | 
					  background: -webkit-gradient(
 | 
				
			||||||
 | 
					    linear,
 | 
				
			||||||
 | 
					    left top,
 | 
				
			||||||
 | 
					    left bottom,
 | 
				
			||||||
 | 
					    color-stop(0, #9d9d9d),
 | 
				
			||||||
 | 
					    color-stop(99%, #c8c8c8)
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  background: -webkit-linear-gradient(top, #9d9d9d, #c8c8c8 99%);
 | 
				
			||||||
 | 
					  background: -o-linear-gradient(top, #9d9d9d 0, #c8c8c8 99%);
 | 
				
			||||||
 | 
					  background: -ms-linear-gradient(top, #9d9d9d 0, #c8c8c8 99%);
 | 
				
			||||||
 | 
					  background: linear-gradient(180deg, #9d9d9d 0, #c8c8c8 99%);
 | 
				
			||||||
 | 
					  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#9d9d9d",endColorstr="#c8c8c8",GradientType=0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					input.vis-configuration.vis-config-range::-moz-range-track {
 | 
				
			||||||
 | 
					  @apply border shadow-[0_0_3px_0_#aaa] h-2.5 w-[300px] rounded-[3px] border-solid border-[#999];
 | 
				
			||||||
 | 
					  background: #dedede;
 | 
				
			||||||
 | 
					  background: -moz-linear-gradient(top, #dedede 0, #c8c8c8 99%);
 | 
				
			||||||
 | 
					  background: -webkit-gradient(
 | 
				
			||||||
 | 
					    linear,
 | 
				
			||||||
 | 
					    left top,
 | 
				
			||||||
 | 
					    left bottom,
 | 
				
			||||||
 | 
					    color-stop(0, #dedede),
 | 
				
			||||||
 | 
					    color-stop(99%, #c8c8c8)
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  background: -webkit-linear-gradient(top, #dedede, #c8c8c8 99%);
 | 
				
			||||||
 | 
					  background: -o-linear-gradient(top, #dedede 0, #c8c8c8 99%);
 | 
				
			||||||
 | 
					  background: -ms-linear-gradient(top, #dedede 0, #c8c8c8 99%);
 | 
				
			||||||
 | 
					  background: linear-gradient(180deg, #dedede 0, #c8c8c8 99%);
 | 
				
			||||||
 | 
					  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#dedede",endColorstr="#c8c8c8",GradientType=0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					input.vis-configuration.vis-config-range::-moz-range-thumb {
 | 
				
			||||||
 | 
					  @apply h-4 w-4 rounded-[50%] border-[none];
 | 
				
			||||||
 | 
					  background: #385380;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					input.vis-configuration.vis-config-range:-moz-focusring {
 | 
				
			||||||
 | 
					  @apply -outline-offset-1;
 | 
				
			||||||
 | 
					  outline: 1px solid #fff;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					input.vis-configuration.vis-config-range::-ms-track {
 | 
				
			||||||
 | 
					  @apply text-transparent h-[5px] w-[300px] border-[6px_0];
 | 
				
			||||||
 | 
					  background: transparent;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					input.vis-configuration.vis-config-range::-ms-fill-lower {
 | 
				
			||||||
 | 
					  @apply rounded-[10px];
 | 
				
			||||||
 | 
					  background: #777;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					input.vis-configuration.vis-config-range::-ms-fill-upper {
 | 
				
			||||||
 | 
					  @apply rounded-[10px];
 | 
				
			||||||
 | 
					  background: #ddd;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					input.vis-configuration.vis-config-range::-ms-thumb {
 | 
				
			||||||
 | 
					  @apply h-4 w-4 rounded-[50%] border-[none];
 | 
				
			||||||
 | 
					  background: #385380;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					input.vis-configuration.vis-config-range:focus::-ms-fill-lower {
 | 
				
			||||||
 | 
					  background: #888;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					input.vis-configuration.vis-config-range:focus::-ms-fill-upper {
 | 
				
			||||||
 | 
					  background: #ccc;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-configuration-popup {
 | 
				
			||||||
 | 
					  @apply rounded text-white text-sm h-[30px] leading-[30px] absolute text-center transition-opacity duration-300 ease-in-out w-[150px] border-2 border-solid border-[#f2faff] after:-mt-2 after:border-[rgba(136,183,213,0)_rgba(136,183,213,0)_rgba(136,183,213,0)_rgba(57,76,89,0.85)] after:border-8 before:-mt-3 before:border-[rgba(194,225,245,0)_rgba(194,225,245,0)_rgba(194,225,245,0)_#f2faff] before:border-[12px];
 | 
				
			||||||
 | 
					  background: rgba(57, 76, 89, 0.85);
 | 
				
			||||||
 | 
					  -webkit-transition: opacity 0.3s ease-in-out;
 | 
				
			||||||
 | 
					  -moz-transition: opacity 0.3s ease-in-out;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-configuration-popup:after,
 | 
				
			||||||
 | 
					.vis-configuration-popup:before {
 | 
				
			||||||
 | 
					  @apply content-["_"] h-0 pointer-events-none absolute w-0 border-[solid] left-full top-2/4;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-panel.vis-background.vis-horizontal .vis-grid.vis-horizontal {
 | 
				
			||||||
 | 
					  @apply h-0 absolute w-full border-b border-solid;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-panel.vis-background.vis-horizontal .vis-grid.vis-minor {
 | 
				
			||||||
 | 
					  @apply border-neutral-200;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-panel.vis-background.vis-horizontal .vis-grid.vis-major {
 | 
				
			||||||
 | 
					  @apply border-[#bfbfbf];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-data-axis .vis-y-axis.vis-major {
 | 
				
			||||||
 | 
					  @apply text-[#4d4d4d] absolute whitespace-nowrap w-full;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-data-axis .vis-y-axis.vis-major.vis-measure {
 | 
				
			||||||
 | 
					  @apply invisible w-auto m-0 p-0 border-0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-data-axis .vis-y-axis.vis-minor {
 | 
				
			||||||
 | 
					  @apply text-[#bebebe] absolute whitespace-nowrap w-full;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-data-axis .vis-y-axis.vis-minor.vis-measure {
 | 
				
			||||||
 | 
					  @apply invisible w-auto m-0 p-0 border-0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-data-axis .vis-y-axis.vis-title {
 | 
				
			||||||
 | 
					  @apply text-[#4d4d4d] absolute text-center whitespace-nowrap bottom-5;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-data-axis .vis-y-axis.vis-title.vis-measure {
 | 
				
			||||||
 | 
					  @apply invisible w-auto m-0 p-0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-data-axis .vis-y-axis.vis-title.vis-left {
 | 
				
			||||||
 | 
					  @apply -rotate-90 origin-[left_bottom] bottom-0;
 | 
				
			||||||
 | 
					  -webkit-transform: rotate(-90deg);
 | 
				
			||||||
 | 
					  -moz-transform: rotate(-90deg);
 | 
				
			||||||
 | 
					  -ms-transform: rotate(-90deg);
 | 
				
			||||||
 | 
					  -o-transform: rotate(-90deg);
 | 
				
			||||||
 | 
					  -webkit-transform-origin: left top;
 | 
				
			||||||
 | 
					  -moz-transform-origin: left top;
 | 
				
			||||||
 | 
					  -ms-transform-origin: left top;
 | 
				
			||||||
 | 
					  -o-transform-origin: left top;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-data-axis .vis-y-axis.vis-title.vis-right {
 | 
				
			||||||
 | 
					  @apply rotate-90 origin-[right_bottom] bottom-0;
 | 
				
			||||||
 | 
					  -webkit-transform: rotate(90deg);
 | 
				
			||||||
 | 
					  -moz-transform: rotate(90deg);
 | 
				
			||||||
 | 
					  -ms-transform: rotate(90deg);
 | 
				
			||||||
 | 
					  -o-transform: rotate(90deg);
 | 
				
			||||||
 | 
					  -webkit-transform-origin: right bottom;
 | 
				
			||||||
 | 
					  -moz-transform-origin: right bottom;
 | 
				
			||||||
 | 
					  -ms-transform-origin: right bottom;
 | 
				
			||||||
 | 
					  -o-transform-origin: right bottom;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-legend {
 | 
				
			||||||
 | 
					  @apply bg-[rgba(247,252,255,0.65)] border shadow-[2px_2px_10px_hsla(0,0%,60%,0.55)] p-[5px] border-solid border-[#b3b3b3];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.vis-legend-text {
 | 
				
			||||||
 | 
					  @apply inline-block whitespace-nowrap;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										1
									
								
								web/src/env.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								web/src/env.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export const ENV = import.meta.env.MODE;
 | 
				
			||||||
							
								
								
									
										138
									
								
								web/src/pages/UIPlayground.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								web/src/pages/UIPlayground.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,138 @@
 | 
				
			|||||||
 | 
					import { useCallback, useMemo, useState } from "react";
 | 
				
			||||||
 | 
					import Heading from "@/components/ui/heading";
 | 
				
			||||||
 | 
					import ActivityScrubber, {
 | 
				
			||||||
 | 
					  ScrubberItem,
 | 
				
			||||||
 | 
					} from "@/components/scrubber/ActivityScrubber";
 | 
				
			||||||
 | 
					import useSWR from "swr";
 | 
				
			||||||
 | 
					import { FrigateConfig } from "@/types/frigateConfig";
 | 
				
			||||||
 | 
					import { Event } from "@/types/event";
 | 
				
			||||||
 | 
					import ActivityIndicator from "@/components/ui/activity-indicator";
 | 
				
			||||||
 | 
					import { useApiHost } from "@/api";
 | 
				
			||||||
 | 
					import TimelineScrubber from "@/components/playground/TimelineScrubber";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Color data
 | 
				
			||||||
 | 
					const colors = [
 | 
				
			||||||
 | 
					  "background",
 | 
				
			||||||
 | 
					  "foreground",
 | 
				
			||||||
 | 
					  "card",
 | 
				
			||||||
 | 
					  "card-foreground",
 | 
				
			||||||
 | 
					  "popover",
 | 
				
			||||||
 | 
					  "popover-foreground",
 | 
				
			||||||
 | 
					  "primary",
 | 
				
			||||||
 | 
					  "primary-foreground",
 | 
				
			||||||
 | 
					  "secondary",
 | 
				
			||||||
 | 
					  "secondary-foreground",
 | 
				
			||||||
 | 
					  "muted",
 | 
				
			||||||
 | 
					  "muted-foreground",
 | 
				
			||||||
 | 
					  "accent",
 | 
				
			||||||
 | 
					  "accent-foreground",
 | 
				
			||||||
 | 
					  "destructive",
 | 
				
			||||||
 | 
					  "destructive-foreground",
 | 
				
			||||||
 | 
					  "border",
 | 
				
			||||||
 | 
					  "input",
 | 
				
			||||||
 | 
					  "ring",
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function ColorSwatch({ name, value }: { name: string; value: string }) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div className="flex items-center mb-2">
 | 
				
			||||||
 | 
					      <div
 | 
				
			||||||
 | 
					        className="w-10 h-10 mr-2 border border-gray-300"
 | 
				
			||||||
 | 
					        style={{ backgroundColor: value }}
 | 
				
			||||||
 | 
					      ></div>
 | 
				
			||||||
 | 
					      <span>{name}</span>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function eventsToScrubberItems(events: Event[]): ScrubberItem[] {
 | 
				
			||||||
 | 
					  const apiHost = useApiHost();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return events.map((event: Event) => ({
 | 
				
			||||||
 | 
					    id: event.id,
 | 
				
			||||||
 | 
					    content: `<div class="flex"><img class="" src="${apiHost}api/events/${event.id}/thumbnail.jpg" /><span>${event.label}</span></div>`,
 | 
				
			||||||
 | 
					    start: new Date(event.start_time * 1000),
 | 
				
			||||||
 | 
					    end: event.end_time ? new Date(event.end_time * 1000) : undefined,
 | 
				
			||||||
 | 
					    type: "box",
 | 
				
			||||||
 | 
					  }));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function UIPlayground() {
 | 
				
			||||||
 | 
					  const { data: config } = useSWR<FrigateConfig>("config");
 | 
				
			||||||
 | 
					  const [timeline, setTimeline] = useState<string | undefined>(undefined);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const onSelect = useCallback(({ items }: { items: string[] }) => {
 | 
				
			||||||
 | 
					    setTimeline(items[0]);
 | 
				
			||||||
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const recentTimestamp = useMemo(() => {
 | 
				
			||||||
 | 
					    const now = new Date();
 | 
				
			||||||
 | 
					    now.setMinutes(now.getMinutes() - 240);
 | 
				
			||||||
 | 
					    return now.getTime() / 1000;
 | 
				
			||||||
 | 
					  }, []);
 | 
				
			||||||
 | 
					  const { data: events } = useSWR<Event[]>([
 | 
				
			||||||
 | 
					    "events",
 | 
				
			||||||
 | 
					    { limit: 10, after: recentTimestamp },
 | 
				
			||||||
 | 
					  ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <>
 | 
				
			||||||
 | 
					      <Heading as="h2">UI Playground</Heading>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <Heading as="h4" className="my-5">
 | 
				
			||||||
 | 
					        Scrubber
 | 
				
			||||||
 | 
					      </Heading>
 | 
				
			||||||
 | 
					      <p className="text-small">
 | 
				
			||||||
 | 
					        Shows the 10 most recent events within the last 4 hours
 | 
				
			||||||
 | 
					      </p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {!config && <ActivityIndicator />}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {config && (
 | 
				
			||||||
 | 
					        <div>
 | 
				
			||||||
 | 
					          {events && events.length > 0 && (
 | 
				
			||||||
 | 
					            <>
 | 
				
			||||||
 | 
					              <ActivityScrubber
 | 
				
			||||||
 | 
					                items={eventsToScrubberItems(events)}
 | 
				
			||||||
 | 
					                selectHandler={onSelect}
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					            </>
 | 
				
			||||||
 | 
					          )}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {config && (
 | 
				
			||||||
 | 
					        <div>
 | 
				
			||||||
 | 
					          {timeline && (
 | 
				
			||||||
 | 
					            <>
 | 
				
			||||||
 | 
					              <TimelineScrubber eventID={timeline} />
 | 
				
			||||||
 | 
					            </>
 | 
				
			||||||
 | 
					          )}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <Heading as="h4" className="my-5">
 | 
				
			||||||
 | 
					        Color scheme
 | 
				
			||||||
 | 
					      </Heading>
 | 
				
			||||||
 | 
					      <p className="text-small">
 | 
				
			||||||
 | 
					        Colors as set by the current theme. See the{" "}
 | 
				
			||||||
 | 
					        <a className="underline" href="https://ui.shadcn.com/docs/theming">
 | 
				
			||||||
 | 
					          shadcn theming docs
 | 
				
			||||||
 | 
					        </a>{" "}
 | 
				
			||||||
 | 
					        for usage.
 | 
				
			||||||
 | 
					      </p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <div className="w-72 my-5">
 | 
				
			||||||
 | 
					        {colors.map((color, index) => (
 | 
				
			||||||
 | 
					          <ColorSwatch
 | 
				
			||||||
 | 
					            key={index}
 | 
				
			||||||
 | 
					            name={color}
 | 
				
			||||||
 | 
					            value={`hsl(var(--${color}))`}
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					        ))}
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default UIPlayground;
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user