├── .gitignore ├── README.md ├── index.html ├── package.json ├── pnpm-lock.yaml ├── public └── .gitkeep └── src ├── demo.js ├── main.js └── style.css /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tweakpane plugin to add Undo/Redo feature to your panes 2 | 3 | ## Installation 4 | 5 | ```bash 6 | npm install github:cosmicshelter/tweakpane-undo-redo-plugin 7 | ``` 8 | 9 | ## Usage 10 | 11 | ```js 12 | import addUndoRedoFeature from 'tweakpane-undo-redo-plugin'; 13 | import * as Tweakpane from 'tweakpane'; 14 | 15 | addUndoRedoFeature(Tweakpane); 16 | ``` 17 | 18 | If needed, you can call clear to empty the action history stack : 19 | 20 | ```js 21 | const undoRedoFeature = addUndoRedoFeature(Tweakpane); 22 | undoRedoFeature.clear(); 23 | ``` 24 | 25 | You can also call destroy to clear the history and remove the keydown event listener : 26 | 27 | ```js 28 | const undoRedoFeature = addUndoRedoFeature(Tweakpane); 29 | undoRedoFeature.destroy(); 30 | ``` 31 | 32 | ## Disclamer 33 | 34 | I had to make my way around the official Tweakpane API to make this work, using internal methods etc... 35 | It might not work properly for Tweakpane versions other than v4.0.5. 36 | 37 | ## Roadmap 38 | 39 | - More testing 40 | - Handle blades 41 | - Handle other plugins 42 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Tweakpane Undo Redo Plugin - Demo 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tweakpane-undo-redo-plugin", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "main": "src/main.js", 7 | "scripts": { 8 | "dev": "vite", 9 | "build": "vite build", 10 | "preview": "vite preview" 11 | }, 12 | "devDependencies": { 13 | "vite": "^6.3.1" 14 | }, 15 | "dependencies": { 16 | "three": "^0.175.0", 17 | "tweakpane": "^4.0.5" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '6.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | dependencies: 8 | three: 9 | specifier: ^0.175.0 10 | version: 0.175.0 11 | tweakpane: 12 | specifier: ^4.0.5 13 | version: 4.0.5 14 | 15 | devDependencies: 16 | vite: 17 | specifier: ^6.3.1 18 | version: 6.3.1 19 | 20 | packages: 21 | 22 | /@esbuild/aix-ppc64@0.25.2: 23 | resolution: {integrity: sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==} 24 | engines: {node: '>=18'} 25 | cpu: [ppc64] 26 | os: [aix] 27 | requiresBuild: true 28 | dev: true 29 | optional: true 30 | 31 | /@esbuild/android-arm64@0.25.2: 32 | resolution: {integrity: sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==} 33 | engines: {node: '>=18'} 34 | cpu: [arm64] 35 | os: [android] 36 | requiresBuild: true 37 | dev: true 38 | optional: true 39 | 40 | /@esbuild/android-arm@0.25.2: 41 | resolution: {integrity: sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==} 42 | engines: {node: '>=18'} 43 | cpu: [arm] 44 | os: [android] 45 | requiresBuild: true 46 | dev: true 47 | optional: true 48 | 49 | /@esbuild/android-x64@0.25.2: 50 | resolution: {integrity: sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==} 51 | engines: {node: '>=18'} 52 | cpu: [x64] 53 | os: [android] 54 | requiresBuild: true 55 | dev: true 56 | optional: true 57 | 58 | /@esbuild/darwin-arm64@0.25.2: 59 | resolution: {integrity: sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==} 60 | engines: {node: '>=18'} 61 | cpu: [arm64] 62 | os: [darwin] 63 | requiresBuild: true 64 | dev: true 65 | optional: true 66 | 67 | /@esbuild/darwin-x64@0.25.2: 68 | resolution: {integrity: sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==} 69 | engines: {node: '>=18'} 70 | cpu: [x64] 71 | os: [darwin] 72 | requiresBuild: true 73 | dev: true 74 | optional: true 75 | 76 | /@esbuild/freebsd-arm64@0.25.2: 77 | resolution: {integrity: sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==} 78 | engines: {node: '>=18'} 79 | cpu: [arm64] 80 | os: [freebsd] 81 | requiresBuild: true 82 | dev: true 83 | optional: true 84 | 85 | /@esbuild/freebsd-x64@0.25.2: 86 | resolution: {integrity: sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==} 87 | engines: {node: '>=18'} 88 | cpu: [x64] 89 | os: [freebsd] 90 | requiresBuild: true 91 | dev: true 92 | optional: true 93 | 94 | /@esbuild/linux-arm64@0.25.2: 95 | resolution: {integrity: sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==} 96 | engines: {node: '>=18'} 97 | cpu: [arm64] 98 | os: [linux] 99 | requiresBuild: true 100 | dev: true 101 | optional: true 102 | 103 | /@esbuild/linux-arm@0.25.2: 104 | resolution: {integrity: sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==} 105 | engines: {node: '>=18'} 106 | cpu: [arm] 107 | os: [linux] 108 | requiresBuild: true 109 | dev: true 110 | optional: true 111 | 112 | /@esbuild/linux-ia32@0.25.2: 113 | resolution: {integrity: sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==} 114 | engines: {node: '>=18'} 115 | cpu: [ia32] 116 | os: [linux] 117 | requiresBuild: true 118 | dev: true 119 | optional: true 120 | 121 | /@esbuild/linux-loong64@0.25.2: 122 | resolution: {integrity: sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==} 123 | engines: {node: '>=18'} 124 | cpu: [loong64] 125 | os: [linux] 126 | requiresBuild: true 127 | dev: true 128 | optional: true 129 | 130 | /@esbuild/linux-mips64el@0.25.2: 131 | resolution: {integrity: sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==} 132 | engines: {node: '>=18'} 133 | cpu: [mips64el] 134 | os: [linux] 135 | requiresBuild: true 136 | dev: true 137 | optional: true 138 | 139 | /@esbuild/linux-ppc64@0.25.2: 140 | resolution: {integrity: sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==} 141 | engines: {node: '>=18'} 142 | cpu: [ppc64] 143 | os: [linux] 144 | requiresBuild: true 145 | dev: true 146 | optional: true 147 | 148 | /@esbuild/linux-riscv64@0.25.2: 149 | resolution: {integrity: sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==} 150 | engines: {node: '>=18'} 151 | cpu: [riscv64] 152 | os: [linux] 153 | requiresBuild: true 154 | dev: true 155 | optional: true 156 | 157 | /@esbuild/linux-s390x@0.25.2: 158 | resolution: {integrity: sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==} 159 | engines: {node: '>=18'} 160 | cpu: [s390x] 161 | os: [linux] 162 | requiresBuild: true 163 | dev: true 164 | optional: true 165 | 166 | /@esbuild/linux-x64@0.25.2: 167 | resolution: {integrity: sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==} 168 | engines: {node: '>=18'} 169 | cpu: [x64] 170 | os: [linux] 171 | requiresBuild: true 172 | dev: true 173 | optional: true 174 | 175 | /@esbuild/netbsd-arm64@0.25.2: 176 | resolution: {integrity: sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==} 177 | engines: {node: '>=18'} 178 | cpu: [arm64] 179 | os: [netbsd] 180 | requiresBuild: true 181 | dev: true 182 | optional: true 183 | 184 | /@esbuild/netbsd-x64@0.25.2: 185 | resolution: {integrity: sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==} 186 | engines: {node: '>=18'} 187 | cpu: [x64] 188 | os: [netbsd] 189 | requiresBuild: true 190 | dev: true 191 | optional: true 192 | 193 | /@esbuild/openbsd-arm64@0.25.2: 194 | resolution: {integrity: sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==} 195 | engines: {node: '>=18'} 196 | cpu: [arm64] 197 | os: [openbsd] 198 | requiresBuild: true 199 | dev: true 200 | optional: true 201 | 202 | /@esbuild/openbsd-x64@0.25.2: 203 | resolution: {integrity: sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==} 204 | engines: {node: '>=18'} 205 | cpu: [x64] 206 | os: [openbsd] 207 | requiresBuild: true 208 | dev: true 209 | optional: true 210 | 211 | /@esbuild/sunos-x64@0.25.2: 212 | resolution: {integrity: sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==} 213 | engines: {node: '>=18'} 214 | cpu: [x64] 215 | os: [sunos] 216 | requiresBuild: true 217 | dev: true 218 | optional: true 219 | 220 | /@esbuild/win32-arm64@0.25.2: 221 | resolution: {integrity: sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==} 222 | engines: {node: '>=18'} 223 | cpu: [arm64] 224 | os: [win32] 225 | requiresBuild: true 226 | dev: true 227 | optional: true 228 | 229 | /@esbuild/win32-ia32@0.25.2: 230 | resolution: {integrity: sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==} 231 | engines: {node: '>=18'} 232 | cpu: [ia32] 233 | os: [win32] 234 | requiresBuild: true 235 | dev: true 236 | optional: true 237 | 238 | /@esbuild/win32-x64@0.25.2: 239 | resolution: {integrity: sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==} 240 | engines: {node: '>=18'} 241 | cpu: [x64] 242 | os: [win32] 243 | requiresBuild: true 244 | dev: true 245 | optional: true 246 | 247 | /@rollup/rollup-android-arm-eabi@4.40.0: 248 | resolution: {integrity: sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==} 249 | cpu: [arm] 250 | os: [android] 251 | requiresBuild: true 252 | dev: true 253 | optional: true 254 | 255 | /@rollup/rollup-android-arm64@4.40.0: 256 | resolution: {integrity: sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==} 257 | cpu: [arm64] 258 | os: [android] 259 | requiresBuild: true 260 | dev: true 261 | optional: true 262 | 263 | /@rollup/rollup-darwin-arm64@4.40.0: 264 | resolution: {integrity: sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==} 265 | cpu: [arm64] 266 | os: [darwin] 267 | requiresBuild: true 268 | dev: true 269 | optional: true 270 | 271 | /@rollup/rollup-darwin-x64@4.40.0: 272 | resolution: {integrity: sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==} 273 | cpu: [x64] 274 | os: [darwin] 275 | requiresBuild: true 276 | dev: true 277 | optional: true 278 | 279 | /@rollup/rollup-freebsd-arm64@4.40.0: 280 | resolution: {integrity: sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==} 281 | cpu: [arm64] 282 | os: [freebsd] 283 | requiresBuild: true 284 | dev: true 285 | optional: true 286 | 287 | /@rollup/rollup-freebsd-x64@4.40.0: 288 | resolution: {integrity: sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==} 289 | cpu: [x64] 290 | os: [freebsd] 291 | requiresBuild: true 292 | dev: true 293 | optional: true 294 | 295 | /@rollup/rollup-linux-arm-gnueabihf@4.40.0: 296 | resolution: {integrity: sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==} 297 | cpu: [arm] 298 | os: [linux] 299 | requiresBuild: true 300 | dev: true 301 | optional: true 302 | 303 | /@rollup/rollup-linux-arm-musleabihf@4.40.0: 304 | resolution: {integrity: sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==} 305 | cpu: [arm] 306 | os: [linux] 307 | requiresBuild: true 308 | dev: true 309 | optional: true 310 | 311 | /@rollup/rollup-linux-arm64-gnu@4.40.0: 312 | resolution: {integrity: sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==} 313 | cpu: [arm64] 314 | os: [linux] 315 | requiresBuild: true 316 | dev: true 317 | optional: true 318 | 319 | /@rollup/rollup-linux-arm64-musl@4.40.0: 320 | resolution: {integrity: sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==} 321 | cpu: [arm64] 322 | os: [linux] 323 | requiresBuild: true 324 | dev: true 325 | optional: true 326 | 327 | /@rollup/rollup-linux-loongarch64-gnu@4.40.0: 328 | resolution: {integrity: sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==} 329 | cpu: [loong64] 330 | os: [linux] 331 | requiresBuild: true 332 | dev: true 333 | optional: true 334 | 335 | /@rollup/rollup-linux-powerpc64le-gnu@4.40.0: 336 | resolution: {integrity: sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==} 337 | cpu: [ppc64] 338 | os: [linux] 339 | requiresBuild: true 340 | dev: true 341 | optional: true 342 | 343 | /@rollup/rollup-linux-riscv64-gnu@4.40.0: 344 | resolution: {integrity: sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==} 345 | cpu: [riscv64] 346 | os: [linux] 347 | requiresBuild: true 348 | dev: true 349 | optional: true 350 | 351 | /@rollup/rollup-linux-riscv64-musl@4.40.0: 352 | resolution: {integrity: sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==} 353 | cpu: [riscv64] 354 | os: [linux] 355 | requiresBuild: true 356 | dev: true 357 | optional: true 358 | 359 | /@rollup/rollup-linux-s390x-gnu@4.40.0: 360 | resolution: {integrity: sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==} 361 | cpu: [s390x] 362 | os: [linux] 363 | requiresBuild: true 364 | dev: true 365 | optional: true 366 | 367 | /@rollup/rollup-linux-x64-gnu@4.40.0: 368 | resolution: {integrity: sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==} 369 | cpu: [x64] 370 | os: [linux] 371 | requiresBuild: true 372 | dev: true 373 | optional: true 374 | 375 | /@rollup/rollup-linux-x64-musl@4.40.0: 376 | resolution: {integrity: sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==} 377 | cpu: [x64] 378 | os: [linux] 379 | requiresBuild: true 380 | dev: true 381 | optional: true 382 | 383 | /@rollup/rollup-win32-arm64-msvc@4.40.0: 384 | resolution: {integrity: sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==} 385 | cpu: [arm64] 386 | os: [win32] 387 | requiresBuild: true 388 | dev: true 389 | optional: true 390 | 391 | /@rollup/rollup-win32-ia32-msvc@4.40.0: 392 | resolution: {integrity: sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==} 393 | cpu: [ia32] 394 | os: [win32] 395 | requiresBuild: true 396 | dev: true 397 | optional: true 398 | 399 | /@rollup/rollup-win32-x64-msvc@4.40.0: 400 | resolution: {integrity: sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==} 401 | cpu: [x64] 402 | os: [win32] 403 | requiresBuild: true 404 | dev: true 405 | optional: true 406 | 407 | /@types/estree@1.0.7: 408 | resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} 409 | dev: true 410 | 411 | /esbuild@0.25.2: 412 | resolution: {integrity: sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==} 413 | engines: {node: '>=18'} 414 | hasBin: true 415 | requiresBuild: true 416 | optionalDependencies: 417 | '@esbuild/aix-ppc64': 0.25.2 418 | '@esbuild/android-arm': 0.25.2 419 | '@esbuild/android-arm64': 0.25.2 420 | '@esbuild/android-x64': 0.25.2 421 | '@esbuild/darwin-arm64': 0.25.2 422 | '@esbuild/darwin-x64': 0.25.2 423 | '@esbuild/freebsd-arm64': 0.25.2 424 | '@esbuild/freebsd-x64': 0.25.2 425 | '@esbuild/linux-arm': 0.25.2 426 | '@esbuild/linux-arm64': 0.25.2 427 | '@esbuild/linux-ia32': 0.25.2 428 | '@esbuild/linux-loong64': 0.25.2 429 | '@esbuild/linux-mips64el': 0.25.2 430 | '@esbuild/linux-ppc64': 0.25.2 431 | '@esbuild/linux-riscv64': 0.25.2 432 | '@esbuild/linux-s390x': 0.25.2 433 | '@esbuild/linux-x64': 0.25.2 434 | '@esbuild/netbsd-arm64': 0.25.2 435 | '@esbuild/netbsd-x64': 0.25.2 436 | '@esbuild/openbsd-arm64': 0.25.2 437 | '@esbuild/openbsd-x64': 0.25.2 438 | '@esbuild/sunos-x64': 0.25.2 439 | '@esbuild/win32-arm64': 0.25.2 440 | '@esbuild/win32-ia32': 0.25.2 441 | '@esbuild/win32-x64': 0.25.2 442 | dev: true 443 | 444 | /fdir@6.4.3(picomatch@4.0.2): 445 | resolution: {integrity: sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==} 446 | peerDependencies: 447 | picomatch: ^3 || ^4 448 | peerDependenciesMeta: 449 | picomatch: 450 | optional: true 451 | dependencies: 452 | picomatch: 4.0.2 453 | dev: true 454 | 455 | /fsevents@2.3.3: 456 | resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} 457 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 458 | os: [darwin] 459 | requiresBuild: true 460 | dev: true 461 | optional: true 462 | 463 | /nanoid@3.3.11: 464 | resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} 465 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 466 | hasBin: true 467 | dev: true 468 | 469 | /picocolors@1.1.1: 470 | resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} 471 | dev: true 472 | 473 | /picomatch@4.0.2: 474 | resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} 475 | engines: {node: '>=12'} 476 | dev: true 477 | 478 | /postcss@8.5.3: 479 | resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} 480 | engines: {node: ^10 || ^12 || >=14} 481 | dependencies: 482 | nanoid: 3.3.11 483 | picocolors: 1.1.1 484 | source-map-js: 1.2.1 485 | dev: true 486 | 487 | /rollup@4.40.0: 488 | resolution: {integrity: sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==} 489 | engines: {node: '>=18.0.0', npm: '>=8.0.0'} 490 | hasBin: true 491 | dependencies: 492 | '@types/estree': 1.0.7 493 | optionalDependencies: 494 | '@rollup/rollup-android-arm-eabi': 4.40.0 495 | '@rollup/rollup-android-arm64': 4.40.0 496 | '@rollup/rollup-darwin-arm64': 4.40.0 497 | '@rollup/rollup-darwin-x64': 4.40.0 498 | '@rollup/rollup-freebsd-arm64': 4.40.0 499 | '@rollup/rollup-freebsd-x64': 4.40.0 500 | '@rollup/rollup-linux-arm-gnueabihf': 4.40.0 501 | '@rollup/rollup-linux-arm-musleabihf': 4.40.0 502 | '@rollup/rollup-linux-arm64-gnu': 4.40.0 503 | '@rollup/rollup-linux-arm64-musl': 4.40.0 504 | '@rollup/rollup-linux-loongarch64-gnu': 4.40.0 505 | '@rollup/rollup-linux-powerpc64le-gnu': 4.40.0 506 | '@rollup/rollup-linux-riscv64-gnu': 4.40.0 507 | '@rollup/rollup-linux-riscv64-musl': 4.40.0 508 | '@rollup/rollup-linux-s390x-gnu': 4.40.0 509 | '@rollup/rollup-linux-x64-gnu': 4.40.0 510 | '@rollup/rollup-linux-x64-musl': 4.40.0 511 | '@rollup/rollup-win32-arm64-msvc': 4.40.0 512 | '@rollup/rollup-win32-ia32-msvc': 4.40.0 513 | '@rollup/rollup-win32-x64-msvc': 4.40.0 514 | fsevents: 2.3.3 515 | dev: true 516 | 517 | /source-map-js@1.2.1: 518 | resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} 519 | engines: {node: '>=0.10.0'} 520 | dev: true 521 | 522 | /three@0.175.0: 523 | resolution: {integrity: sha512-nNE3pnTHxXN/Phw768u0Grr7W4+rumGg/H6PgeseNJojkJtmeHJfZWi41Gp2mpXl1pg1pf1zjwR4McM1jTqkpg==} 524 | dev: false 525 | 526 | /tinyglobby@0.2.12: 527 | resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==} 528 | engines: {node: '>=12.0.0'} 529 | dependencies: 530 | fdir: 6.4.3(picomatch@4.0.2) 531 | picomatch: 4.0.2 532 | dev: true 533 | 534 | /tweakpane@4.0.5: 535 | resolution: {integrity: sha512-rxEXdSI+ArlG1RyO6FghC4ZUX8JkEfz8F3v1JuteXSV0pEtHJzyo07fcDG+NsJfN5L39kSbCYbB9cBGHyuI/tQ==} 536 | dev: false 537 | 538 | /vite@6.3.1: 539 | resolution: {integrity: sha512-kkzzkqtMESYklo96HKKPE5KKLkC1amlsqt+RjFMlX2AvbRB/0wghap19NdBxxwGZ+h/C6DLCrcEphPIItlGrRQ==} 540 | engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} 541 | hasBin: true 542 | peerDependencies: 543 | '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 544 | jiti: '>=1.21.0' 545 | less: '*' 546 | lightningcss: ^1.21.0 547 | sass: '*' 548 | sass-embedded: '*' 549 | stylus: '*' 550 | sugarss: '*' 551 | terser: ^5.16.0 552 | tsx: ^4.8.1 553 | yaml: ^2.4.2 554 | peerDependenciesMeta: 555 | '@types/node': 556 | optional: true 557 | jiti: 558 | optional: true 559 | less: 560 | optional: true 561 | lightningcss: 562 | optional: true 563 | sass: 564 | optional: true 565 | sass-embedded: 566 | optional: true 567 | stylus: 568 | optional: true 569 | sugarss: 570 | optional: true 571 | terser: 572 | optional: true 573 | tsx: 574 | optional: true 575 | yaml: 576 | optional: true 577 | dependencies: 578 | esbuild: 0.25.2 579 | fdir: 6.4.3(picomatch@4.0.2) 580 | picomatch: 4.0.2 581 | postcss: 8.5.3 582 | rollup: 4.40.0 583 | tinyglobby: 0.2.12 584 | optionalDependencies: 585 | fsevents: 2.3.3 586 | dev: true 587 | -------------------------------------------------------------------------------- /public/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmicshelter/tweakpane-undo-redo-plugin/634ab65e8ba0a3918fe6b872784ec579aa21ffdf/public/.gitkeep -------------------------------------------------------------------------------- /src/demo.js: -------------------------------------------------------------------------------- 1 | import './style.css' 2 | import * as THREE from 'three'; 3 | import * as Tweakpane from 'tweakpane'; 4 | // import addUndoRedoFeature from './main'; 5 | 6 | // addUndoRedoFeature(Tweakpane); 7 | 8 | /** 9 | * Setup Three 10 | */ 11 | const scene = new THREE.Scene(); 12 | 13 | const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); 14 | camera.position.z = 5; 15 | 16 | const renderer = new THREE.WebGLRenderer(); 17 | renderer.setSize(window.innerWidth, window.innerHeight); 18 | document.body.appendChild(renderer.domElement); 19 | 20 | const geometry = new THREE.BoxGeometry(1, 1, 1); 21 | const material = new THREE.MeshNormalMaterial(); 22 | const cube = new THREE.Mesh(geometry, material); 23 | scene.add(cube); 24 | 25 | /** 26 | * Render loop 27 | */ 28 | function animate() { 29 | cube.rotation.x += 0.005; 30 | cube.rotation.y += 0.005; 31 | 32 | renderer.render(scene, camera); 33 | } 34 | 35 | renderer.setAnimationLoop(animate); 36 | 37 | /** 38 | * Setup Pane 39 | */ 40 | const pane = new Tweakpane.Pane({ title: 'Tweakpane Undo / Redo' }); 41 | 42 | const cubeFolder = pane.addFolder({ title: 'Cube' }); 43 | cubeFolder.addBinding(cube.scale, 'x'); 44 | cubeFolder.addBinding(cube.scale, 'y'); 45 | cubeFolder.addBinding(cube.scale, 'z'); 46 | 47 | const cameraFolder = pane.addFolder({ title: 'Camera' }); 48 | const cameraTabs = cameraFolder.addTab({ pages: [{ title: 'Position' }, { title: 'Settings' }] }); 49 | 50 | cameraTabs.pages[0].addBinding(camera.position, 'x'); 51 | cameraTabs.pages[0].addBinding(camera.position, 'y'); 52 | cameraTabs.pages[0].addBinding(camera.position, 'z'); 53 | 54 | cameraTabs.pages[1].addBinding(camera, 'near').on('change', () => { camera.updateProjectionMatrix() }); 55 | cameraTabs.pages[1].addBinding(camera, 'far').on('change', () => { camera.updateProjectionMatrix() }); 56 | cameraTabs.pages[1].addBinding(camera, 'fov').on('change', () => { camera.updateProjectionMatrix() }); -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | export default function addUndoRedoFeature(Tweakpane) { 2 | let undoStack = []; 3 | let redoStack = []; 4 | 5 | function undo() { 6 | if (undoStack.length === 0) return; 7 | 8 | const undoData = undoStack.pop(); 9 | undoData.binding.isUndoAction = true; 10 | undoData.binding.controller.value.setRawValue(undoData.value); 11 | } 12 | 13 | function redo() { 14 | if (redoStack.length === 0) return; 15 | 16 | const redoData = redoStack.pop(); 17 | redoData.binding.isRedoAction = true; 18 | redoData.binding.controller.value.setRawValue(redoData.value); 19 | } 20 | 21 | function bindingUndoRedoMiddleware(binding, options) { 22 | binding.isReadOnly = options[2] && options[2].readonly; 23 | 24 | if (binding.isReadOnly) return binding; 25 | 26 | binding.previousValues = []; 27 | 28 | binding.controller.value.emitter.on('beforechange', (e) => { 29 | const previousValue = e.sender.rawValue; 30 | binding.previousValues.push(previousValue); 31 | }); 32 | 33 | binding.on('change', (e) => { 34 | if (!e.last) return; 35 | 36 | if (binding.isUndoAction) { 37 | redoStack.push({ binding, value: binding.previousValues[0] }); 38 | binding.isUndoAction = false; 39 | } else if (binding.isRedoAction) { 40 | undoStack.push({ binding, value: binding.previousValues[0] }); 41 | binding.isRedoAction = false; 42 | } else { 43 | redoStack = []; 44 | undoStack.push({ binding, value: binding.previousValues[0] }); 45 | } 46 | 47 | binding.previousValues = []; 48 | }); 49 | 50 | return binding; 51 | } 52 | 53 | function keydownHandler(e) { 54 | const cmd = e.metaKey || e.ctrlKey; 55 | const z = e.key === 'z'; 56 | const Z = e.key === 'Z'; 57 | 58 | if (cmd && z) undo(); 59 | else if (cmd && Z) redo(); 60 | } 61 | 62 | window.addEventListener('keydown', keydownHandler); 63 | 64 | Object.assign(Tweakpane.TabPageApi.prototype, { 65 | addBinding: function(...options) { 66 | const binding = this.rackApi_.addBinding(...options); 67 | return bindingUndoRedoMiddleware(binding, options); 68 | }, 69 | }); 70 | 71 | Object.assign(Tweakpane.FolderApi.prototype, { 72 | addBinding: function(...options) { 73 | const binding = this.rackApi_.addBinding(...options); 74 | return bindingUndoRedoMiddleware(binding, options); 75 | }, 76 | }); 77 | 78 | return { 79 | clear() { 80 | redoStack = []; 81 | undoStack = []; 82 | }, 83 | destroy: () => { 84 | redoStack = []; 85 | undoStack = []; 86 | window.removeEventListener('keydown', keydownHandler); 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- 1 | canvas { 2 | position: fixed; 3 | left: 0; 4 | top: 0; 5 | } 6 | 7 | .tp-dfwv { 8 | right: 400px !important; 9 | } --------------------------------------------------------------------------------