mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-09-30 11:12:35 +02:00
Housekeeping
This commit is contained in:
@@ -1,4 +0,0 @@
|
||||
node_modules/
|
||||
output/
|
||||
*.webm
|
||||
*.mp4
|
||||
@@ -1,50 +0,0 @@
|
||||
# Bambuddy Demo Video Recorder
|
||||
|
||||
Automated demo video recording using Playwright.
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
cd demo-video
|
||||
npm install
|
||||
npm run install-browsers
|
||||
```
|
||||
|
||||
## Recording
|
||||
|
||||
### Record with visible browser (recommended for debugging)
|
||||
```bash
|
||||
npm run record
|
||||
```
|
||||
|
||||
### Record headless (faster, no window)
|
||||
```bash
|
||||
npm run record:headless
|
||||
```
|
||||
|
||||
### Custom URL
|
||||
```bash
|
||||
DEMO_URL=https://your-bambuddy.example.com npm run record
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
Videos are saved to `output/` as `.webm` files.
|
||||
|
||||
### Convert to MP4
|
||||
```bash
|
||||
ffmpeg -i output/video.webm -c:v libx264 -crf 23 demo.mp4
|
||||
```
|
||||
|
||||
### Convert with better quality
|
||||
```bash
|
||||
ffmpeg -i output/video.webm -c:v libx264 -crf 18 -preset slow demo.mp4
|
||||
```
|
||||
|
||||
## Customization
|
||||
|
||||
Edit `record-demo.ts` to:
|
||||
- Adjust timing (TIMING constants)
|
||||
- Add/remove page demonstrations
|
||||
- Customize interactions per page
|
||||
- Change viewport resolution (CONFIG)
|
||||
Generated
-537
@@ -1,537 +0,0 @@
|
||||
{
|
||||
"name": "bambuddy-demo-video",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "bambuddy-demo-video",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"playwright": "^1.40.0",
|
||||
"tsx": "^4.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz",
|
||||
"integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz",
|
||||
"integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz",
|
||||
"integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz",
|
||||
"integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz",
|
||||
"integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz",
|
||||
"integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz",
|
||||
"integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz",
|
||||
"integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz",
|
||||
"integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openharmony-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openharmony"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz",
|
||||
"integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz",
|
||||
"integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==",
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.27.2",
|
||||
"@esbuild/android-arm": "0.27.2",
|
||||
"@esbuild/android-arm64": "0.27.2",
|
||||
"@esbuild/android-x64": "0.27.2",
|
||||
"@esbuild/darwin-arm64": "0.27.2",
|
||||
"@esbuild/darwin-x64": "0.27.2",
|
||||
"@esbuild/freebsd-arm64": "0.27.2",
|
||||
"@esbuild/freebsd-x64": "0.27.2",
|
||||
"@esbuild/linux-arm": "0.27.2",
|
||||
"@esbuild/linux-arm64": "0.27.2",
|
||||
"@esbuild/linux-ia32": "0.27.2",
|
||||
"@esbuild/linux-loong64": "0.27.2",
|
||||
"@esbuild/linux-mips64el": "0.27.2",
|
||||
"@esbuild/linux-ppc64": "0.27.2",
|
||||
"@esbuild/linux-riscv64": "0.27.2",
|
||||
"@esbuild/linux-s390x": "0.27.2",
|
||||
"@esbuild/linux-x64": "0.27.2",
|
||||
"@esbuild/netbsd-arm64": "0.27.2",
|
||||
"@esbuild/netbsd-x64": "0.27.2",
|
||||
"@esbuild/openbsd-arm64": "0.27.2",
|
||||
"@esbuild/openbsd-x64": "0.27.2",
|
||||
"@esbuild/openharmony-arm64": "0.27.2",
|
||||
"@esbuild/sunos-x64": "0.27.2",
|
||||
"@esbuild/win32-arm64": "0.27.2",
|
||||
"@esbuild/win32-ia32": "0.27.2",
|
||||
"@esbuild/win32-x64": "0.27.2"
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/get-tsconfig": {
|
||||
"version": "4.13.0",
|
||||
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz",
|
||||
"integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==",
|
||||
"dependencies": {
|
||||
"resolve-pkg-maps": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.57.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz",
|
||||
"integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.57.0"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.57.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz",
|
||||
"integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==",
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve-pkg-maps": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
|
||||
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
|
||||
"funding": {
|
||||
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx": {
|
||||
"version": "4.21.0",
|
||||
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
|
||||
"integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
|
||||
"dependencies": {
|
||||
"esbuild": "~0.27.0",
|
||||
"get-tsconfig": "^4.7.5"
|
||||
},
|
||||
"bin": {
|
||||
"tsx": "dist/cli.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"name": "bambuddy-demo-video",
|
||||
"version": "1.0.0",
|
||||
"description": "Automated demo video recording for Bambuddy",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"record": "npx tsx record-demo.ts",
|
||||
"record:headless": "HEADLESS=true npx tsx record-demo.ts",
|
||||
"install-browsers": "npx playwright install chromium"
|
||||
},
|
||||
"dependencies": {
|
||||
"playwright": "^1.40.0",
|
||||
"tsx": "^4.7.0"
|
||||
}
|
||||
}
|
||||
@@ -1,566 +0,0 @@
|
||||
import { chromium, Page, Browser, BrowserContext } from 'playwright';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
// Configuration
|
||||
const CONFIG = {
|
||||
baseUrl: process.env.DEMO_URL || 'http://localhost:8000',
|
||||
headless: process.env.HEADLESS === 'true',
|
||||
slowMo: 50, // Slow down actions for visibility
|
||||
viewportWidth: 1920,
|
||||
viewportHeight: 1080,
|
||||
outputDir: path.join(__dirname, 'output'),
|
||||
};
|
||||
|
||||
// Timing helpers (in ms)
|
||||
const TIMING = {
|
||||
pageLoad: 1500, // Wait after page navigation
|
||||
shortPause: 500, // Brief pause between actions
|
||||
mediumPause: 1000, // Standard pause for visibility
|
||||
longPause: 2000, // Longer pause for important features
|
||||
modalOpen: 800, // Wait for modal animations
|
||||
scrollPause: 600, // Pause after scrolling
|
||||
};
|
||||
|
||||
async function wait(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
async function scrollDown(page: Page, pixels: number = 300): Promise<void> {
|
||||
await page.mouse.wheel(0, pixels);
|
||||
await wait(TIMING.scrollPause);
|
||||
}
|
||||
|
||||
async function scrollToTop(page: Page): Promise<void> {
|
||||
await page.evaluate(() => window.scrollTo({ top: 0, behavior: 'smooth' }));
|
||||
await wait(TIMING.scrollPause);
|
||||
}
|
||||
|
||||
async function hoverElement(page: Page, selector: string): Promise<void> {
|
||||
const element = page.locator(selector).first();
|
||||
if (await element.isVisible()) {
|
||||
await element.hover();
|
||||
await wait(TIMING.shortPause);
|
||||
}
|
||||
}
|
||||
|
||||
async function clickIfVisible(page: Page, selector: string): Promise<boolean> {
|
||||
const element = page.locator(selector).first();
|
||||
if (await element.isVisible()) {
|
||||
await element.click();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async function closeModalIfOpen(page: Page): Promise<void> {
|
||||
// Try to close any open modal by pressing Escape
|
||||
await page.keyboard.press('Escape');
|
||||
await wait(TIMING.shortPause);
|
||||
}
|
||||
|
||||
async function blurSensitiveContent(page: Page): Promise<void> {
|
||||
// Use JavaScript to find and blur email addresses
|
||||
await page.evaluate(() => {
|
||||
// Find all spans and check for email patterns
|
||||
document.querySelectorAll('span').forEach(el => {
|
||||
const text = el.textContent || '';
|
||||
// Check if this specific element (not children) contains an email
|
||||
if (el.childNodes.length === 1 && el.childNodes[0].nodeType === Node.TEXT_NODE) {
|
||||
if (text.includes('@') && text.includes('.')) {
|
||||
(el as HTMLElement).style.filter = 'blur(6px)';
|
||||
(el as HTMLElement).style.userSelect = 'none';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Also find "Connected as" text and blur the next sibling span
|
||||
document.querySelectorAll('span').forEach(el => {
|
||||
if (el.textContent?.includes('Connected as')) {
|
||||
const emailSpan = el.querySelector('span');
|
||||
if (emailSpan) {
|
||||
(emailSpan as HTMLElement).style.filter = 'blur(6px)';
|
||||
(emailSpan as HTMLElement).style.userSelect = 'none';
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Page Scenarios
|
||||
// ============================================================================
|
||||
|
||||
async function demoPrintersPage(page: Page): Promise<void> {
|
||||
console.log('📷 Demonstrating Printers page...');
|
||||
await page.goto(CONFIG.baseUrl);
|
||||
await wait(TIMING.pageLoad);
|
||||
|
||||
// Hover over printer cards to show interactions
|
||||
const printerCards = page.locator('.group').filter({ has: page.locator('img') });
|
||||
const cardCount = await printerCards.count();
|
||||
console.log(` Found ${cardCount} printer cards`);
|
||||
|
||||
for (let i = 0; i < Math.min(cardCount, 2); i++) {
|
||||
const card = printerCards.nth(i);
|
||||
if (await card.isVisible()) {
|
||||
await card.hover();
|
||||
await wait(TIMING.mediumPause);
|
||||
|
||||
// Try clicking on card to expand/show details
|
||||
await card.click();
|
||||
await wait(TIMING.mediumPause);
|
||||
}
|
||||
}
|
||||
|
||||
// Look for AMS section and hover over slots
|
||||
const amsSlots = page.locator('[class*="ams"], [class*="AMS"]').first();
|
||||
if (await amsSlots.isVisible()) {
|
||||
await amsSlots.hover();
|
||||
await wait(TIMING.mediumPause);
|
||||
}
|
||||
|
||||
// Try to open camera modal
|
||||
const cameraIcon = page.locator('svg[class*="lucide-video"], button:has(svg)').first();
|
||||
if (await cameraIcon.isVisible()) {
|
||||
await cameraIcon.click();
|
||||
await wait(TIMING.longPause);
|
||||
await page.keyboard.press('Escape');
|
||||
await wait(TIMING.shortPause);
|
||||
}
|
||||
|
||||
// Try to open MQTT debug modal
|
||||
const debugButton = page.locator('button:has-text("Debug"), button:has-text("MQTT")').first();
|
||||
if (await debugButton.isVisible()) {
|
||||
await debugButton.click();
|
||||
await wait(TIMING.longPause);
|
||||
await page.keyboard.press('Escape');
|
||||
await wait(TIMING.shortPause);
|
||||
}
|
||||
|
||||
// Scroll to show more printers
|
||||
await scrollDown(page, 400);
|
||||
await wait(TIMING.mediumPause);
|
||||
await scrollToTop(page);
|
||||
}
|
||||
|
||||
async function demoArchivesPage(page: Page): Promise<void> {
|
||||
console.log('📁 Demonstrating Archives page...');
|
||||
await page.goto(`${CONFIG.baseUrl}/archives`);
|
||||
await wait(TIMING.pageLoad);
|
||||
|
||||
// Show view mode toggle (grid/list/calendar)
|
||||
const viewToggle = page.locator('button:has(svg[class*="grid"]), button:has(svg[class*="list"])');
|
||||
if (await viewToggle.first().isVisible()) {
|
||||
await viewToggle.first().click();
|
||||
await wait(TIMING.mediumPause);
|
||||
await viewToggle.first().click(); // Toggle back
|
||||
await wait(TIMING.shortPause);
|
||||
}
|
||||
|
||||
// Use search
|
||||
const searchInput = page.locator('input[placeholder*="Search"], input[type="search"]').first();
|
||||
if (await searchInput.isVisible()) {
|
||||
await searchInput.click();
|
||||
await searchInput.fill('engine');
|
||||
await wait(TIMING.longPause);
|
||||
await searchInput.clear();
|
||||
await wait(TIMING.shortPause);
|
||||
}
|
||||
|
||||
// Show filter dropdowns
|
||||
const filterButtons = page.locator('button:has-text("Printer"), button:has-text("Material"), button:has-text("Filter")');
|
||||
if (await filterButtons.first().isVisible()) {
|
||||
await filterButtons.first().click();
|
||||
await wait(TIMING.mediumPause);
|
||||
await page.keyboard.press('Escape');
|
||||
await wait(TIMING.shortPause);
|
||||
}
|
||||
|
||||
// Right-click to show context menu
|
||||
const archiveCard = page.locator('.group').filter({ has: page.locator('img') }).first();
|
||||
if (await archiveCard.isVisible()) {
|
||||
await archiveCard.click({ button: 'right' });
|
||||
await wait(TIMING.longPause);
|
||||
await page.keyboard.press('Escape');
|
||||
await wait(TIMING.shortPause);
|
||||
}
|
||||
|
||||
// Click on archive to open edit modal
|
||||
if (await archiveCard.isVisible()) {
|
||||
await archiveCard.dblclick();
|
||||
await wait(TIMING.longPause);
|
||||
await page.keyboard.press('Escape');
|
||||
await wait(TIMING.shortPause);
|
||||
}
|
||||
|
||||
// Scroll to show more archives
|
||||
await scrollDown(page, 500);
|
||||
await wait(TIMING.mediumPause);
|
||||
await scrollToTop(page);
|
||||
}
|
||||
|
||||
async function demoQueuePage(page: Page): Promise<void> {
|
||||
console.log('📋 Demonstrating Queue page...');
|
||||
await page.goto(`${CONFIG.baseUrl}/queue`);
|
||||
await wait(TIMING.pageLoad);
|
||||
|
||||
// Show filter dropdowns
|
||||
const printerFilter = page.locator('button:has-text("Printer"), select').first();
|
||||
if (await printerFilter.isVisible()) {
|
||||
await printerFilter.click();
|
||||
await wait(TIMING.mediumPause);
|
||||
await page.keyboard.press('Escape');
|
||||
await wait(TIMING.shortPause);
|
||||
}
|
||||
|
||||
// Show sort controls
|
||||
const sortButton = page.locator('button:has-text("Sort"), button:has(svg[class*="arrow"])').first();
|
||||
if (await sortButton.isVisible()) {
|
||||
await sortButton.click();
|
||||
await wait(TIMING.mediumPause);
|
||||
}
|
||||
|
||||
// Hover over queue items to show drag handles
|
||||
const queueItems = page.locator('[draggable="true"], .group').first();
|
||||
if (await queueItems.isVisible()) {
|
||||
await queueItems.hover();
|
||||
await wait(TIMING.mediumPause);
|
||||
}
|
||||
|
||||
// Scroll through queue
|
||||
await scrollDown(page, 300);
|
||||
await wait(TIMING.mediumPause);
|
||||
await scrollToTop(page);
|
||||
}
|
||||
|
||||
async function demoStatsPage(page: Page): Promise<void> {
|
||||
console.log('📊 Demonstrating Stats page...');
|
||||
await page.goto(`${CONFIG.baseUrl}/stats`);
|
||||
await wait(TIMING.pageLoad);
|
||||
|
||||
// Let charts animate
|
||||
await wait(TIMING.longPause);
|
||||
|
||||
// Show export dropdown
|
||||
const exportButton = page.locator('button:has-text("Export"), button:has(svg[class*="download"])').first();
|
||||
if (await exportButton.isVisible()) {
|
||||
await exportButton.click();
|
||||
await wait(TIMING.mediumPause);
|
||||
await page.keyboard.press('Escape');
|
||||
await wait(TIMING.shortPause);
|
||||
}
|
||||
|
||||
// Scroll through stats widgets
|
||||
await scrollDown(page, 400);
|
||||
await wait(TIMING.mediumPause);
|
||||
await scrollDown(page, 400);
|
||||
await wait(TIMING.mediumPause);
|
||||
await scrollDown(page, 400);
|
||||
await wait(TIMING.mediumPause);
|
||||
await scrollToTop(page);
|
||||
}
|
||||
|
||||
async function demoProfilesPage(page: Page): Promise<void> {
|
||||
console.log('⚙️ Demonstrating Profiles page...');
|
||||
|
||||
// Start blur loop BEFORE navigating
|
||||
let blurring = true;
|
||||
const blurLoop = async () => {
|
||||
while (blurring) {
|
||||
try {
|
||||
await page.evaluate(() => {
|
||||
document.querySelectorAll('span').forEach(el => {
|
||||
if (el.textContent?.includes('Connected as')) {
|
||||
const emailSpan = el.querySelector('span');
|
||||
if (emailSpan) {
|
||||
(emailSpan as HTMLElement).style.filter = 'blur(6px)';
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch { /* page might be navigating */ }
|
||||
await new Promise(r => setTimeout(r, 30));
|
||||
}
|
||||
};
|
||||
|
||||
// Start blur loop in background
|
||||
const blurPromise = blurLoop();
|
||||
|
||||
await page.goto(`${CONFIG.baseUrl}/profiles`);
|
||||
await wait(TIMING.pageLoad);
|
||||
|
||||
// Show Cloud Profiles section
|
||||
await wait(TIMING.mediumPause);
|
||||
|
||||
// Click on K-Profiles tab if available
|
||||
try {
|
||||
const kProfilesTab = page.locator('button:has-text("K-Profile"), button:has-text("K Profile")').first();
|
||||
if (await kProfilesTab.isVisible({ timeout: 1000 })) {
|
||||
await kProfilesTab.click({ timeout: 2000 });
|
||||
await wait(TIMING.mediumPause);
|
||||
await scrollDown(page, 300);
|
||||
await wait(TIMING.shortPause);
|
||||
await scrollToTop(page);
|
||||
}
|
||||
} catch { /* skip */ }
|
||||
|
||||
// Click back to Cloud Profiles
|
||||
try {
|
||||
const cloudTab = page.locator('button:has-text("Cloud")').first();
|
||||
if (await cloudTab.isVisible({ timeout: 1000 })) {
|
||||
await cloudTab.click({ timeout: 2000 });
|
||||
await wait(TIMING.mediumPause);
|
||||
}
|
||||
} catch { /* skip */ }
|
||||
|
||||
// Show preset filter types (if visible) - use force to bypass overlays
|
||||
const presetFilters = page.locator('button:has-text("Filament"), button:has-text("Process"), button:has-text("Machine")');
|
||||
for (let i = 0; i < 3; i++) {
|
||||
try {
|
||||
const filter = presetFilters.nth(i);
|
||||
if (await filter.isVisible({ timeout: 1000 })) {
|
||||
await filter.click({ force: true, timeout: 2000 });
|
||||
await wait(TIMING.shortPause);
|
||||
}
|
||||
} catch { /* skip if not visible or blocked */ }
|
||||
}
|
||||
|
||||
await scrollDown(page, 300);
|
||||
await wait(TIMING.shortPause);
|
||||
await scrollToTop(page);
|
||||
|
||||
// Stop blur loop
|
||||
blurring = false;
|
||||
await blurPromise;
|
||||
}
|
||||
|
||||
async function demoMaintenancePage(page: Page): Promise<void> {
|
||||
console.log('🔧 Demonstrating Maintenance page...');
|
||||
await page.goto(`${CONFIG.baseUrl}/maintenance`);
|
||||
await wait(TIMING.pageLoad);
|
||||
|
||||
// Show status tab (default)
|
||||
await wait(TIMING.mediumPause);
|
||||
|
||||
// Expand a printer section if available
|
||||
const expandButton = page.locator('button:has(svg[class*="chevron"])').first();
|
||||
if (await expandButton.isVisible()) {
|
||||
await expandButton.click();
|
||||
await wait(TIMING.mediumPause);
|
||||
}
|
||||
|
||||
// Scroll through status
|
||||
await scrollDown(page, 300);
|
||||
await wait(TIMING.shortPause);
|
||||
await scrollToTop(page);
|
||||
|
||||
// Click Settings tab
|
||||
const settingsTab = page.locator('button:has-text("Settings"), [role="tab"]:has-text("Settings")').first();
|
||||
if (await settingsTab.isVisible()) {
|
||||
await settingsTab.click();
|
||||
await wait(TIMING.mediumPause);
|
||||
|
||||
// Scroll through settings
|
||||
await scrollDown(page, 300);
|
||||
await wait(TIMING.shortPause);
|
||||
await scrollToTop(page);
|
||||
}
|
||||
|
||||
// Go back to Status tab
|
||||
const statusTab = page.locator('button:has-text("Status"), [role="tab"]:has-text("Status")').first();
|
||||
if (await statusTab.isVisible()) {
|
||||
await statusTab.click();
|
||||
await wait(TIMING.shortPause);
|
||||
}
|
||||
}
|
||||
|
||||
async function demoProjectsPage(page: Page): Promise<void> {
|
||||
console.log('📂 Demonstrating Projects page...');
|
||||
await page.goto(`${CONFIG.baseUrl}/projects`);
|
||||
await wait(TIMING.pageLoad);
|
||||
|
||||
// Click through status filter buttons
|
||||
const statusFilters = ['Active', 'Completed', 'Archived', 'All'];
|
||||
for (const status of statusFilters) {
|
||||
const filterBtn = page.locator(`button:has-text("${status}")`).first();
|
||||
if (await filterBtn.isVisible()) {
|
||||
await filterBtn.click();
|
||||
await wait(TIMING.shortPause);
|
||||
}
|
||||
}
|
||||
|
||||
// Click on a project to go to detail page
|
||||
const projectCard = page.locator('.group, [class*="project"]').filter({ has: page.locator('h3, h2') }).first();
|
||||
if (await projectCard.isVisible()) {
|
||||
await projectCard.click();
|
||||
await wait(TIMING.pageLoad);
|
||||
|
||||
// Scroll through project detail
|
||||
await scrollDown(page, 300);
|
||||
await wait(TIMING.mediumPause);
|
||||
|
||||
// Look for tabs in project detail (BOM, Attachments, Prints)
|
||||
const detailTabs = ['BOM', 'Attachments', 'Prints', 'Notes'];
|
||||
for (const tabName of detailTabs) {
|
||||
const tab = page.locator(`button:has-text("${tabName}"), [role="tab"]:has-text("${tabName}")`).first();
|
||||
if (await tab.isVisible()) {
|
||||
await tab.click();
|
||||
await wait(TIMING.mediumPause);
|
||||
}
|
||||
}
|
||||
|
||||
await scrollToTop(page);
|
||||
}
|
||||
}
|
||||
|
||||
async function demoSettingsPage(page: Page): Promise<void> {
|
||||
console.log('⚙️ Demonstrating Settings page...');
|
||||
await page.goto(`${CONFIG.baseUrl}/settings`);
|
||||
await wait(TIMING.pageLoad);
|
||||
|
||||
// Define the 6 tabs to click through
|
||||
const tabs = ['General', 'Plugs', 'Notifications', 'Filament', 'API', 'Virtual'];
|
||||
|
||||
for (const tabName of tabs) {
|
||||
const tab = page.locator(`button:has-text("${tabName}"), [role="tab"]:has-text("${tabName}")`).first();
|
||||
if (await tab.isVisible()) {
|
||||
await tab.click();
|
||||
await wait(TIMING.mediumPause);
|
||||
|
||||
// Scroll through tab content
|
||||
await scrollDown(page, 300);
|
||||
await wait(TIMING.shortPause);
|
||||
await scrollToTop(page);
|
||||
}
|
||||
}
|
||||
|
||||
// Go back to General tab and show a modal
|
||||
const generalTab = page.locator('button:has-text("General")').first();
|
||||
if (await generalTab.isVisible()) {
|
||||
await generalTab.click();
|
||||
await wait(TIMING.shortPause);
|
||||
}
|
||||
|
||||
// Try to open backup modal
|
||||
const backupButton = page.locator('button:has-text("Backup")').first();
|
||||
if (await backupButton.isVisible()) {
|
||||
await backupButton.click();
|
||||
await wait(TIMING.longPause);
|
||||
await page.keyboard.press('Escape');
|
||||
await wait(TIMING.shortPause);
|
||||
}
|
||||
|
||||
// Go to Plugs tab and show add modal
|
||||
const plugsTab = page.locator('button:has-text("Plugs")').first();
|
||||
if (await plugsTab.isVisible()) {
|
||||
await plugsTab.click();
|
||||
await wait(TIMING.shortPause);
|
||||
|
||||
const addPlugButton = page.locator('button:has-text("Add"), button:has(svg[class*="plus"])').first();
|
||||
if (await addPlugButton.isVisible()) {
|
||||
await addPlugButton.click();
|
||||
await wait(TIMING.longPause);
|
||||
await page.keyboard.press('Escape');
|
||||
await wait(TIMING.shortPause);
|
||||
}
|
||||
}
|
||||
|
||||
// Go to Notifications tab and show add modal
|
||||
const notifTab = page.locator('button:has-text("Notifications")').first();
|
||||
if (await notifTab.isVisible()) {
|
||||
await notifTab.click();
|
||||
await wait(TIMING.shortPause);
|
||||
|
||||
const addNotifButton = page.locator('button:has-text("Add"), button:has(svg[class*="plus"])').first();
|
||||
if (await addNotifButton.isVisible()) {
|
||||
await addNotifButton.click();
|
||||
await wait(TIMING.longPause);
|
||||
await page.keyboard.press('Escape');
|
||||
await wait(TIMING.shortPause);
|
||||
}
|
||||
}
|
||||
|
||||
await scrollToTop(page);
|
||||
}
|
||||
|
||||
async function demoSystemPage(page: Page): Promise<void> {
|
||||
console.log('💻 Demonstrating System page...');
|
||||
await page.goto(`${CONFIG.baseUrl}/system`);
|
||||
await wait(TIMING.pageLoad);
|
||||
|
||||
// Show system info
|
||||
await wait(TIMING.mediumPause);
|
||||
await scrollDown(page, 300);
|
||||
await wait(TIMING.shortPause);
|
||||
await scrollToTop(page);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Main Recording Function
|
||||
// ============================================================================
|
||||
|
||||
async function recordDemo(): Promise<void> {
|
||||
console.log('🎬 Starting Bambuddy demo recording...');
|
||||
console.log(` URL: ${CONFIG.baseUrl}`);
|
||||
console.log(` Resolution: ${CONFIG.viewportWidth}x${CONFIG.viewportHeight}`);
|
||||
console.log(` Headless: ${CONFIG.headless}`);
|
||||
console.log('');
|
||||
|
||||
const browser: Browser = await chromium.launch({
|
||||
headless: CONFIG.headless,
|
||||
slowMo: CONFIG.slowMo,
|
||||
});
|
||||
|
||||
const context: BrowserContext = await browser.newContext({
|
||||
viewport: {
|
||||
width: CONFIG.viewportWidth,
|
||||
height: CONFIG.viewportHeight,
|
||||
},
|
||||
recordVideo: {
|
||||
dir: CONFIG.outputDir,
|
||||
size: {
|
||||
width: CONFIG.viewportWidth,
|
||||
height: CONFIG.viewportHeight,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const page: Page = await context.newPage();
|
||||
|
||||
try {
|
||||
// Run through all page demos
|
||||
await demoPrintersPage(page);
|
||||
await demoArchivesPage(page);
|
||||
await demoQueuePage(page);
|
||||
await demoStatsPage(page);
|
||||
await demoProfilesPage(page);
|
||||
await demoMaintenancePage(page);
|
||||
await demoProjectsPage(page);
|
||||
await demoSettingsPage(page);
|
||||
await demoSystemPage(page);
|
||||
|
||||
// Return to home page for closing shot
|
||||
console.log('🏠 Returning to home page...');
|
||||
await page.goto(CONFIG.baseUrl);
|
||||
await wait(TIMING.longPause);
|
||||
|
||||
console.log('✅ Demo recording completed!');
|
||||
} catch (error) {
|
||||
console.error('❌ Error during recording:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
await page.close();
|
||||
await context.close();
|
||||
await browser.close();
|
||||
}
|
||||
|
||||
console.log(`\n📹 Video saved to: ${CONFIG.outputDir}/`);
|
||||
console.log(' (Playwright saves as .webm, convert with ffmpeg if needed)');
|
||||
console.log(' Example: ffmpeg -i video.webm -c:v libx264 demo.mp4');
|
||||
}
|
||||
|
||||
// Run the recording
|
||||
recordDemo().catch(console.error);
|
||||
@@ -114,17 +114,17 @@ export function SmartPlugCard({ plug, onEdit }: SmartPlugCardProps) {
|
||||
<CardContent className="p-4">
|
||||
{/* Header Row */}
|
||||
<div className="flex items-start justify-between mb-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={`p-2 rounded-lg ${isReachable ? (isOn ? 'bg-bambu-green/20' : 'bg-bambu-dark') : 'bg-red-500/20'}`}>
|
||||
<div className="flex items-center gap-3 min-w-0 flex-1">
|
||||
<div className={`p-2 rounded-lg flex-shrink-0 ${isReachable ? (isOn ? 'bg-bambu-green/20' : 'bg-bambu-dark') : 'bg-red-500/20'}`}>
|
||||
{plug.plug_type === 'homeassistant' ? (
|
||||
<Home className={`w-5 h-5 ${isReachable ? (isOn ? 'text-bambu-green' : 'text-bambu-gray') : 'text-red-400'}`} />
|
||||
) : (
|
||||
<Plug className={`w-5 h-5 ${isReachable ? (isOn ? 'text-bambu-green' : 'text-bambu-gray') : 'text-red-400'}`} />
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-medium text-white">{plug.name}</h3>
|
||||
<p className="text-sm text-bambu-gray">
|
||||
<div className="min-w-0">
|
||||
<h3 className="font-medium text-white truncate" title={plug.name}>{plug.name}</h3>
|
||||
<p className="text-sm text-bambu-gray truncate">
|
||||
{plug.plug_type === 'homeassistant' ? plug.ha_entity_id : plug.ip_address}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
File diff suppressed because one or more lines are too long
+1
-1
@@ -23,7 +23,7 @@
|
||||
|
||||
<!-- Splash screens for iOS -->
|
||||
<link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
|
||||
<script type="module" crossorigin src="/assets/index-CBd7jYRK.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-cNEBVebe.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-BQ1e_nWl.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
Reference in New Issue
Block a user