From fd46ec1efd7aded4b3209d9ee9b54017462fb430 Mon Sep 17 00:00:00 2001 From: Julian Bilcke Date: Mon, 29 Jul 2024 14:40:14 +0200 Subject: [PATCH] improve window layout --- package-lock.json | 689 ++++++++++++++++- package.json | 28 +- .../images/logos}/logo-desaturated.png | 0 public/images/logos/logo-discord-2.png | 3 + public/images/logos/logo-discord-3.png | 3 + .../images/logos}/logo-no-bg.png | 0 {src/app => public/images/logos}/logo-v2.png | 0 {src/app => public/images/logos}/logo-v2.xcf | 0 src/app/main.tsx | 60 +- .../editors/EntityEditor/EntityTree/index.tsx | 12 + .../EntityEditor/EntityTree/useEntityTree.ts | 15 +- src/components/forms/FormSection.tsx | 6 +- .../top-menu/ToggleFullScreen/index.tsx | 62 ++ .../toolbars/top-menu/ToggleView/index.tsx | 6 +- .../top-menu/ToggleWindowLayout/index.tsx | 67 ++ src/components/toolbars/top-menu/index.tsx | 7 + .../toolbars/top-menu/view/index.tsx | 74 +- src/components/windows/index.tsx | 708 +++++++++--------- src/lib/core/constants.ts | 2 +- src/lib/hooks/useFullscreenStatus.ts | 97 +-- src/services/windows/useWindows.ts | 7 +- 21 files changed, 1361 insertions(+), 485 deletions(-) rename {src/app => public/images/logos}/logo-desaturated.png (100%) create mode 100644 public/images/logos/logo-discord-2.png create mode 100644 public/images/logos/logo-discord-3.png rename {src/app => public/images/logos}/logo-no-bg.png (100%) rename {src/app => public/images/logos}/logo-v2.png (100%) rename {src/app => public/images/logos}/logo-v2.xcf (100%) create mode 100644 src/components/toolbars/top-menu/ToggleFullScreen/index.tsx create mode 100644 src/components/toolbars/top-menu/ToggleWindowLayout/index.tsx diff --git a/package-lock.json b/package-lock.json index bec92c5b..3a14c3da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,9 +11,9 @@ "dependencies": { "@aitube/broadway": "0.1.2", "@aitube/clap": "0.1.2", - "@aitube/clapper-services": "0.1.2-7", + "@aitube/clapper-services": "0.1.2-8", "@aitube/engine": "0.1.2", - "@aitube/timeline": "0.1.2-1", + "@aitube/timeline": "0.1.2-2", "@fal-ai/serverless-client": "^0.13.0", "@ffmpeg/ffmpeg": "^0.12.10", "@ffmpeg/util": "^0.12.1", @@ -184,12 +184,12 @@ } }, "node_modules/@aitube/clapper-services": { - "version": "0.1.2-7", - "resolved": "https://registry.npmjs.org/@aitube/clapper-services/-/clapper-services-0.1.2-7.tgz", - "integrity": "sha512-xqt8XG8R7SHG+7bBb1+5yHzbfkpndyGiJOV8Agz5wfcuOKHuSk8EOrcKc7oB2t/AVrnkQSp9ph+6Gnf1mOXLpg==", + "version": "0.1.2-8", + "resolved": "https://registry.npmjs.org/@aitube/clapper-services/-/clapper-services-0.1.2-8.tgz", + "integrity": "sha512-LjC9J8HZ/Dt94NzkEWRnm7v51d9Hncc/SjhzkMGEc6/FGnu97IhmPsI2DCI61a+SUlqYn14Nt+FdT949NHvVLw==", "peerDependencies": { "@aitube/clap": "0.1.2", - "@aitube/timeline": "0.1.2-1", + "@aitube/timeline": "0.1.2-2", "@monaco-editor/react": "4.6.0", "monaco-editor": "0.50.0", "react": "*", @@ -215,9 +215,9 @@ } }, "node_modules/@aitube/timeline": { - "version": "0.1.2-1", - "resolved": "https://registry.npmjs.org/@aitube/timeline/-/timeline-0.1.2-1.tgz", - "integrity": "sha512-7Y4ExWf2GA2p7a0W8Yt6pfIEa79I0oXiyhGWY+b2BN0SXKHOwEYYnlNlDBQhTDILsHsYgKnvvABt2iQdnt4xxA==", + "version": "0.1.2-2", + "resolved": "https://registry.npmjs.org/@aitube/timeline/-/timeline-0.1.2-2.tgz", + "integrity": "sha512-4ep73A0C7zEMreQ2jpCvJbCJJNQgQZBYxTQqJmoiZg5DPiIXFzkOTOYIJuhPu4wZX/Q14wPgpyXmF3UJ1x3ujA==", "dependencies": { "date-fns": "^3.6.0", "react-virtualized-auto-sizer": "^1.0.24" @@ -1547,9 +1547,9 @@ } }, "node_modules/@babel/traverse": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.0.tgz", - "integrity": "sha512-ubALThHQy4GCf6mbb+5ZRNmLLCI7bJ3f8Q6LHBSRlSKSWj5a7dSUzJBLv3VuIhFrFPgjF4IzPF567YG/HSCdZA==", + "version": "7.25.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.1.tgz", + "integrity": "sha512-LrHHoWq08ZpmmFqBAzN+hUdWwy5zt7FGa/hVwMcOqW6OVtwqaoD5utfuGYU87JYxdZgLUvktAsn37j/sYR9siA==", "dependencies": { "@babel/code-frame": "^7.24.7", "@babel/generator": "^7.25.0", @@ -2806,6 +2806,15 @@ "node": ">=14.14" } }, + "node_modules/@emnapi/runtime": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.2.0.tgz", + "integrity": "sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@emotion/is-prop-valid": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.0.tgz", @@ -2829,6 +2838,70 @@ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@esbuild/darwin-arm64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", @@ -2845,6 +2918,294 @@ "node": ">=12" } }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -3238,6 +3599,27 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.2.tgz", + "integrity": "sha512-cmhQ1J4qVhfmS6szYW7RT+gLJq9dH2i4maq+qyXayUSn9/3iY2ZeWpbAgSpSVbV2E1JUL2Gg7pwnYQ1h8rQIog==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.28", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@img/sharp-libvips-linux-x64": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.2.tgz", @@ -3369,6 +3751,31 @@ "@img/sharp-libvips-linux-arm64": "1.0.2" } }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.4.tgz", + "integrity": "sha512-h3RAL3siQoyzSoH36tUeS0PDmb5wINKGYzcLB5C6DIiAn2F3udeFAum+gj8IbA/82+8RGCTn7XW8WTFnqag4tQ==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.31", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.2" + } + }, "node_modules/@img/sharp-linux-x64": { "version": "0.33.4", "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.4.tgz", @@ -3444,6 +3851,27 @@ "@img/sharp-libvips-linuxmusl-x64": "1.0.2" } }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.4", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.4.tgz", + "integrity": "sha512-Bmmauh4sXUsUqkleQahpdNXKvo+wa1V9KhT2pDA4VJGKwnKMJXiSTGphn0gnJrlooda0QxCtXc6RX1XAU6hMnQ==", + "cpu": [ + "wasm32" + ], + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.1.1" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@img/sharp-win32-ia32": { "version": "0.33.4", "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.4.tgz", @@ -3487,11 +3915,11 @@ } }, "node_modules/@inquirer/confirm": { - "version": "3.1.17", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-3.1.17.tgz", - "integrity": "sha512-qCpt/AABzPynz8tr69VDvhcjwmzAryipWXtW8Vi6m651da4H/d0Bdn55LkxXD7Rp2gfgxvxzTdb66AhIA8gzBA==", + "version": "3.1.18", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-3.1.18.tgz", + "integrity": "sha512-axDSeAtgRfMAOnI2NXJAcBliknRiPHBPBh8VpofFW2vSt5nxU/IoNcWfNBIs1LFwICyLzbvGjF3fd+rYLSU11w==", "dependencies": { - "@inquirer/core": "^9.0.5", + "@inquirer/core": "^9.0.6", "@inquirer/type": "^1.5.1" }, "engines": { @@ -3499,14 +3927,14 @@ } }, "node_modules/@inquirer/core": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-9.0.5.tgz", - "integrity": "sha512-QWG41I7vn62O9stYKg/juKXt1PEbr/4ZZCPb4KgXDQGwgA9M5NBTQ7FnOvT1ridbxkm/wTxLCNraUs7y47pIRQ==", + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-9.0.6.tgz", + "integrity": "sha512-pmwIJJrtOBmP29JLPkdq5ORGGaSzOwZbashYyME20sD5AITiy2j3LFsnTXXuiqPIkq4XjQYOHzaExAmqjyU1Cg==", "dependencies": { "@inquirer/figures": "^1.0.5", "@inquirer/type": "^1.5.1", "@types/mute-stream": "^0.0.4", - "@types/node": "^20.14.11", + "@types/node": "^20.14.13", "@types/wrap-ansi": "^3.0.0", "ansi-escapes": "^4.3.2", "cli-spinners": "^2.9.2", @@ -6108,6 +6536,32 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.19.1.tgz", + "integrity": "sha512-XzqSg714++M+FXhHfXpS1tDnNZNpgxxuGZWlRG/jSj+VEPmZ0yg6jV4E0AL3uyBKxO8mO3xtOsP5mQ+XLfrlww==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.19.1.tgz", + "integrity": "sha512-thFUbkHteM20BGShD6P08aungq4irbIZKUNbG70LN8RkO7YztcGPiKTTGZS7Kw+x5h8hOXs0i4OaHwFxlpQN6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, "node_modules/@rollup/rollup-darwin-arm64": { "version": "4.19.1", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.19.1.tgz", @@ -6121,6 +6575,175 @@ "darwin" ] }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.19.1.tgz", + "integrity": "sha512-4T42heKsnbjkn7ovYiAdDVRRWZLU9Kmhdt6HafZxFcUdpjlBlxj4wDrt1yFWLk7G4+E+8p2C9tcmSu0KA6auGA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.19.1.tgz", + "integrity": "sha512-MXg1xp+e5GhZ3Vit1gGEyoC+dyQUBy2JgVQ+3hUrD9wZMkUw/ywgkpK7oZgnB6kPpGrxJ41clkPPnsknuD6M2Q==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.19.1.tgz", + "integrity": "sha512-DZNLwIY4ftPSRVkJEaxYkq7u2zel7aah57HESuNkUnz+3bZHxwkCUkrfS2IWC1sxK6F2QNIR0Qr/YXw7nkF3Pw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.19.1.tgz", + "integrity": "sha512-C7evongnjyxdngSDRRSQv5GvyfISizgtk9RM+z2biV5kY6S/NF/wta7K+DanmktC5DkuaJQgoKGf7KUDmA7RUw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.19.1.tgz", + "integrity": "sha512-89tFWqxfxLLHkAthAcrTs9etAoBFRduNfWdl2xUs/yLV+7XDrJ5yuXMHptNqf1Zw0UCA3cAutkAiAokYCkaPtw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.19.1.tgz", + "integrity": "sha512-PromGeV50sq+YfaisG8W3fd+Cl6mnOOiNv2qKKqKCpiiEke2KiKVyDqG/Mb9GWKbYMHj5a01fq/qlUR28PFhCQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.19.1.tgz", + "integrity": "sha512-/1BmHYh+iz0cNCP0oHCuF8CSiNj0JOGf0jRlSo3L/FAyZyG2rGBuKpkZVH9YF+x58r1jgWxvm1aRg3DHrLDt6A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.19.1.tgz", + "integrity": "sha512-0cYP5rGkQWRZKy9/HtsWVStLXzCF3cCBTRI+qRL8Z+wkYlqN7zrSYm6FuY5Kd5ysS5aH0q5lVgb/WbG4jqXN1Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.19.1.tgz", + "integrity": "sha512-XUXeI9eM8rMP8aGvii/aOOiMvTs7xlCosq9xCjcqI9+5hBxtjDpD+7Abm1ZhVIFE1J2h2VIg0t2DX/gjespC2Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.19.1.tgz", + "integrity": "sha512-V7cBw/cKXMfEVhpSvVZhC+iGifD6U1zJ4tbibjjN+Xi3blSXaj/rJynAkCFFQfoG6VZrAiP7uGVzL440Q6Me2Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.19.1.tgz", + "integrity": "sha512-88brja2vldW/76jWATlBqHEoGjJLRnP0WOEKAUbMcXaAZnemNhlAHSyj4jIwMoP2T750LE9lblvD4e2jXleZsA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.19.1.tgz", + "integrity": "sha512-LdxxcqRVSXi6k6JUrTah1rHuaupoeuiv38du8Mt4r4IPer3kwlTo+RuvfE8KzZ/tL6BhaPlzJ3835i6CxrFIRQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.19.1.tgz", + "integrity": "sha512-2bIrL28PcK3YCqD9anGxDxamxdiJAxA+l7fWIwM5o8UqNy1t3d1NdAweO2XhA0KTDJ5aH1FsuiT5+7VhtHliXg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@rushstack/eslint-patch": { "version": "1.10.4", "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.4.tgz", @@ -7023,9 +7646,9 @@ } }, "node_modules/@types/node": { - "version": "20.14.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.12.tgz", - "integrity": "sha512-r7wNXakLeSsGT0H1AU863vS2wa5wBOK4bWMjZz2wj+8nBx+m5PeIn0k8AloSLpRuiwdRQZwarZqHE4FNArPuJQ==", + "version": "20.14.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.13.tgz", + "integrity": "sha512-+bHoGiZb8UiQ0+WEtmph2IWQCjIqg8MDZMAV+ppRRhUZnquF5mQkP/9vpSwJClEiSM/C7fZZExPzfU0vJTyp8w==", "dependencies": { "undici-types": "~5.26.4" } @@ -7399,7 +8022,7 @@ }, "node_modules/@xenova/transformers": { "version": "3.0.0-alpha.0", - "resolved": "git+ssh://git@github.com/xenova/transformers.js.git#c6aeb4be1bc1cdfa72e9d050f77b97dc9c8af362", + "resolved": "git+ssh://git@github.com/xenova/transformers.js.git#38a3bf6dab2265d9f0c2f613064535863194e6b9", "dependencies": { "@huggingface/jinja": "^0.2.2", "onnxruntime-web": "^1.18.0", @@ -9812,9 +10435,9 @@ } }, "node_modules/detect-gpu": { - "version": "5.0.40", - "resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.40.tgz", - "integrity": "sha512-5v4jDN/ERdZZitD29UiLjV9Q9+lDfw2OhEJACIqnvdWulVZCy2K6EwonZ/VKyo4YMqvSIzGIDmojX3jGL3dLpA==", + "version": "5.0.41", + "resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.41.tgz", + "integrity": "sha512-0avjQwm8zyDPLmmp2PlaUxOWp/CNLbOU4t61x1IOTmBvC7UO+NMWDlEJcIjtbRBSnulC2ote81Xyillssam0CA==", "dependencies": { "webgl-constants": "^1.1.1" } @@ -11637,9 +12260,9 @@ } }, "node_modules/fast-xml-parser": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.0.tgz", - "integrity": "sha512-kLY3jFlwIYwBNDojclKsNAC12sfD6NwW74QB2CoNGPvtVxjliYehVunB3HYyNi+n4Tt1dAcgwYvmKF/Z18flqg==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", "funding": [ { "type": "github", @@ -20295,9 +20918,9 @@ } }, "node_modules/zod-to-json-schema": { - "version": "3.23.1", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.23.1.tgz", - "integrity": "sha512-oT9INvydob1XV0v1d2IadrR74rLtDInLvDFfAa1CG0Pmg/vxATk7I2gSelfj271mbzeM4Da0uuDQE/Nkj3DWNw==", + "version": "3.23.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.23.2.tgz", + "integrity": "sha512-uSt90Gzc/tUfyNqxnjlfBs8W6WSGpNBv0rVsNxP/BVSMHMKGdthPYff4xtCHYloJGM0CFxFsb3NbC0eqPhfImw==", "peerDependencies": { "zod": "^3.23.3" } diff --git a/package.json b/package.json index 22bde703..7b4974f7 100644 --- a/package.json +++ b/package.json @@ -38,9 +38,9 @@ "dependencies": { "@aitube/broadway": "0.1.2", "@aitube/clap": "0.1.2", - "@aitube/clapper-services": "0.1.2-7", + "@aitube/clapper-services": "0.1.2-8", "@aitube/engine": "0.1.2", - "@aitube/timeline": "0.1.2-1", + "@aitube/timeline": "0.1.2-2", "@fal-ai/serverless-client": "^0.13.0", "@ffmpeg/ffmpeg": "^0.12.10", "@ffmpeg/util": "^0.12.1", @@ -165,23 +165,23 @@ "vitest": "^2.0.2" }, "optionalDependencies": { - "@img/sharp-win32-ia32": "0.33.4", - "@img/sharp-win32-x64": "0.33.4", "@img/sharp-darwin-arm64": "0.33.4", "@img/sharp-darwin-x64": "0.33.4", + "@img/sharp-libvips-darwin-arm64": "1.0.2", + "@img/sharp-libvips-darwin-x64": "1.0.2", + "@img/sharp-libvips-linux-arm": "1.0.2", + "@img/sharp-libvips-linux-arm64": "1.0.2", + "@img/sharp-libvips-linux-x64": "1.0.2", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.2", + "@img/sharp-libvips-linuxmusl-x64": "1.0.2", + "@img/sharp-libvips-win32-ia32": "1.0.2", + "@img/sharp-libvips-win32-x64": "1.0.2", "@img/sharp-linux-arm": "0.33.4", "@img/sharp-linux-arm64": "0.33.4", "@img/sharp-linux-x64": "0.33.4", - "@img/sharp-linuxmusl-x64": "0.33.4", "@img/sharp-linuxmusl-arm64": "0.33.4", - "@img/sharp-libvips-win32-x64": "1.0.2", - "@img/sharp-libvips-win32-ia32": "1.0.2", - "@img/sharp-libvips-linux-arm": "1.0.2", - "@img/sharp-libvips-linux-x64": "1.0.2", - "@img/sharp-libvips-linux-arm64": "1.0.2", - "@img/sharp-libvips-linuxmusl-x64": "1.0.2", - "@img/sharp-libvips-linuxmusl-arm64": "1.0.2", - "@img/sharp-libvips-darwin-x64": "1.0.2", - "@img/sharp-libvips-darwin-arm64": "1.0.2" + "@img/sharp-linuxmusl-x64": "0.33.4", + "@img/sharp-win32-ia32": "0.33.4", + "@img/sharp-win32-x64": "0.33.4" } } diff --git a/src/app/logo-desaturated.png b/public/images/logos/logo-desaturated.png similarity index 100% rename from src/app/logo-desaturated.png rename to public/images/logos/logo-desaturated.png diff --git a/public/images/logos/logo-discord-2.png b/public/images/logos/logo-discord-2.png new file mode 100644 index 00000000..bc4de75d --- /dev/null +++ b/public/images/logos/logo-discord-2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:914019608bd1f1107e9d7d6477f26b98b07c55844dd7f53bb54fe8062c708bfa +size 568110 diff --git a/public/images/logos/logo-discord-3.png b/public/images/logos/logo-discord-3.png new file mode 100644 index 00000000..1129e22b --- /dev/null +++ b/public/images/logos/logo-discord-3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f26ab09e96f7b92408b73a1d212311f674d48f96a7a29b96a6cb27f8884c9212 +size 308874 diff --git a/src/app/logo-no-bg.png b/public/images/logos/logo-no-bg.png similarity index 100% rename from src/app/logo-no-bg.png rename to public/images/logos/logo-no-bg.png diff --git a/src/app/logo-v2.png b/public/images/logos/logo-v2.png similarity index 100% rename from src/app/logo-v2.png rename to public/images/logos/logo-v2.png diff --git a/src/app/logo-v2.xcf b/public/images/logos/logo-v2.xcf similarity index 100% rename from src/app/logo-v2.xcf rename to public/images/logos/logo-v2.xcf diff --git a/src/app/main.tsx b/src/app/main.tsx index 82e6bccb..39213027 100644 --- a/src/app/main.tsx +++ b/src/app/main.tsx @@ -118,10 +118,12 @@ function MainContent() { @@ -130,10 +132,12 @@ function MainContent() { @@ -142,10 +146,12 @@ function MainContent() { @@ -155,10 +161,12 @@ function MainContent() { @@ -168,10 +176,12 @@ function MainContent() { @@ -180,10 +190,12 @@ function MainContent() { diff --git a/src/components/editors/EntityEditor/EntityTree/index.tsx b/src/components/editors/EntityEditor/EntityTree/index.tsx index 66030894..f4888593 100644 --- a/src/components/editors/EntityEditor/EntityTree/index.tsx +++ b/src/components/editors/EntityEditor/EntityTree/index.tsx @@ -1,11 +1,15 @@ 'use client' +import { useEffect } from 'react' +import { useTimeline } from '@aitube/timeline' + import { cn } from '@/lib/utils' import { isClapEntity } from '@/components/tree-browsers/utils/isSomething' import { TreeNodeItem, LibraryNodeType } from '@/components/tree-browsers/types' import { Tree } from '@/components/core/tree' import { useEntityTree } from './useEntityTree' +import { ClapEntity } from '@aitube/clap' export function EntityTree({ className = '', @@ -15,7 +19,15 @@ export function EntityTree({ const libraryTreeRoot = useEntityTree((s) => s.libraryTreeRoot) const selectTreeNode = useEntityTree((s) => s.selectTreeNode) const selectedTreeNodeId = useEntityTree((s) => s.selectedTreeNodeId) + const setProjectEntities = useEntityTree((s) => s.setProjectEntities) + + const entitiesChanged: number = useTimeline((s) => s.entitiesChanged) + const entities: ClapEntity[] = useTimeline((s) => s.entities) + useEffect(() => { + console.log('loading entities:', entities) + setProjectEntities(entities) + }, [entities, entitiesChanged, entities.length]) /** * handle click on tree node * yes, this is where the magic happens! diff --git a/src/components/editors/EntityEditor/EntityTree/useEntityTree.ts b/src/components/editors/EntityEditor/EntityTree/useEntityTree.ts index d8f3fe8c..daa5d866 100644 --- a/src/components/editors/EntityEditor/EntityTree/useEntityTree.ts +++ b/src/components/editors/EntityEditor/EntityTree/useEntityTree.ts @@ -1,7 +1,7 @@ 'use client' import { create } from 'zustand' -import { ClapEntity, UUID } from '@aitube/clap' +import { ClapEntity, ClapSegmentCategory, UUID } from '@aitube/clap' import { LibraryTreeNode, TreeNodeItem, @@ -11,6 +11,7 @@ import { icons } from '@/components/icons' import { getAppropriateIcon } from '@/components/icons/getAppropriateIcon' import { collectionClassName, + itemClassName, libraryClassName, } from '@/components/tree-browsers/style/treeNodeStyles' @@ -33,7 +34,7 @@ export const useEntityTree = create<{ * @param collections * @returns */ - // setProjectLibrary: (collections: ProjectEntityCollection[]) => void + setProjectEntities: (entities: ClapEntity[]) => Promise /** * Load entity collections (characters, locations..) from the Clapper community into the tree @@ -109,11 +110,10 @@ export const useEntityTree = create<{ }) }, - /* setProjectEntities: async (entities: ClapEntity[]) => { const characters: LibraryTreeNode = { id: UUID(), - nodeType: 'LIB_NODE_GENERIC_COLLECTION', + nodeType: 'ENTITY_TREE_NODE_LIST_ENTITIES', data: undefined, label: 'Characters', icon: icons.characters, @@ -124,7 +124,7 @@ export const useEntityTree = create<{ const locations: LibraryTreeNode = { id: UUID(), - nodeType: 'LIB_NODE_GENERIC_COLLECTION', + nodeType: 'ENTITY_TREE_NODE_LIST_ENTITIES', data: undefined, label: 'Locations', icon: icons.location, @@ -135,7 +135,7 @@ export const useEntityTree = create<{ const misc: LibraryTreeNode = { id: UUID(), - nodeType: 'LIB_NODE_GENERIC_COLLECTION', + nodeType: 'ENTITY_TREE_NODE_LIST_ENTITIES', data: undefined, label: 'Misc', icon: icons.misc, @@ -146,7 +146,7 @@ export const useEntityTree = create<{ entities.forEach((entity) => { const node: LibraryTreeNode = { - nodeType: TreeNodeEntityItem, + nodeType: 'ENTITY_TREE_NODE_ITEM_ENTITY', id: entity.id, data: entity, label: entity.label, @@ -165,6 +165,7 @@ export const useEntityTree = create<{ }) }, + /* setCommunityCollections: (collections: CommunityEntityCollection[]) => { // TODO: implement this diff --git a/src/components/forms/FormSection.tsx b/src/components/forms/FormSection.tsx index 677d466a..0ac12306 100644 --- a/src/components/forms/FormSection.tsx +++ b/src/components/forms/FormSection.tsx @@ -19,8 +19,10 @@ export function FormSection({ className )} > -

{label}

-
{children}
+

{label}

+
+ {children} +
) } diff --git a/src/components/toolbars/top-menu/ToggleFullScreen/index.tsx b/src/components/toolbars/top-menu/ToggleFullScreen/index.tsx new file mode 100644 index 00000000..065df6a8 --- /dev/null +++ b/src/components/toolbars/top-menu/ToggleFullScreen/index.tsx @@ -0,0 +1,62 @@ +import { useEffect } from 'react' +import { RiFullscreenLine, RiFullscreenExitLine } from 'react-icons/ri' + +import { cn } from '@/lib/utils' +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from '@/components/ui/tooltip' +import { useFullscreenStatus } from '@/lib/hooks' + +export function ToggleFullScreen({ + className = '', +}: { + className?: string +} = {}) { + const [isFullscreen, setFullscreen, ref] = useFullscreenStatus() + + // we want the whole body to become fullscreen + // TODO: use pointer lock, to prevent the mouse from going up + useEffect(() => { + if (typeof window !== 'undefined') { + ref.current = document.body + } + }, [ref]) + + return ( + + +
{ + setFullscreen(!isFullscreen) + }} + > +
+ + +
+
+
+ + Toggle fullscreen + +
+ ) +} diff --git a/src/components/toolbars/top-menu/ToggleView/index.tsx b/src/components/toolbars/top-menu/ToggleView/index.tsx index 46bf4fbf..9cb8f92f 100644 --- a/src/components/toolbars/top-menu/ToggleView/index.tsx +++ b/src/components/toolbars/top-menu/ToggleView/index.tsx @@ -23,7 +23,7 @@ export function ToggleView({
{ setVisible(!isVisible) @@ -32,7 +32,9 @@ export function ToggleView({
diff --git a/src/components/toolbars/top-menu/ToggleWindowLayout/index.tsx b/src/components/toolbars/top-menu/ToggleWindowLayout/index.tsx new file mode 100644 index 00000000..b1f45a55 --- /dev/null +++ b/src/components/toolbars/top-menu/ToggleWindowLayout/index.tsx @@ -0,0 +1,67 @@ +import { VscMultipleWindows } from 'react-icons/vsc' +import { CiGrid32 } from 'react-icons/ci' + +import { GiStrawberry } from 'react-icons/gi' +import { UIWindowLayout } from '@aitube/clapper-services' + +import { cn } from '@/lib/utils' +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from '@/components/ui/tooltip' +import { useUI } from '@/services' +import { BiSolidWindowAlt } from 'react-icons/bi' + +export function ToggleWindowLayout({ + className = '', +}: { + className?: string +} = {}) { + const windowLayout = useUI((s) => s.windowLayout) + const setWindowLayout = useUI((s) => s.setWindowLayout) + + return ( + + +
{ + setWindowLayout( + windowLayout === UIWindowLayout.FLYING + ? UIWindowLayout.GRID + : UIWindowLayout.FLYING + ) + }} + > +
+ + +
+
+
+ + Toggle layout + +
+ ) +} diff --git a/src/components/toolbars/top-menu/index.tsx b/src/components/toolbars/top-menu/index.tsx index 93d8acfb..920f41f9 100644 --- a/src/components/toolbars/top-menu/index.tsx +++ b/src/components/toolbars/top-menu/index.tsx @@ -24,6 +24,8 @@ import { import { ToggleView } from './ToggleView' import { UIWindowLayout } from '@aitube/clapper-services' import { Tasks } from '../bottom-bar/tasks' +import { ToggleWindowLayout } from './ToggleWindowLayout' +import { ToggleFullScreen } from './ToggleFullScreen' export function TopMenu() { const isBusyResolving = useResolver((s) => s.isBusyResolving) @@ -38,6 +40,7 @@ export function TopMenu() { const setShowAssistant = useUI((s) => s.setShowAssistant) const setIsTopMenuOpen = useUI((s) => s.setIsTopMenuOpen) const windowLayout = useUI((s) => s.windowLayout) + const setWindowLayout = useUI((s) => s.setWindowLayout) const hasBetaAccess = useUI((s) => s.hasBetaAccess) return ( @@ -107,6 +110,10 @@ export function TopMenu() { )} + + + +
diff --git a/src/components/toolbars/top-menu/view/index.tsx b/src/components/toolbars/top-menu/view/index.tsx index 7696bc16..6f9eb42f 100644 --- a/src/components/toolbars/top-menu/view/index.tsx +++ b/src/components/toolbars/top-menu/view/index.tsx @@ -5,6 +5,7 @@ import { useEffect } from 'react' import { MenubarCheckboxItem, MenubarContent, + MenubarItem, MenubarMenu, MenubarSeparator, MenubarTrigger, @@ -13,6 +14,10 @@ import { useFullscreenStatus } from '@/lib/hooks' import { useUI } from '@/services/ui' import { ThemeList } from '../lists/ThemeList' import { UIWindowLayout } from '@aitube/clapper-services' +import { cn } from '@/lib/utils' +import { GiStrawberry } from 'react-icons/gi' +import { BiSolidWindowAlt } from 'react-icons/bi' +import { RiFullscreenExitLine, RiFullscreenLine } from 'react-icons/ri' export function TopMenuView() { const [isFullscreen, setFullscreen, ref] = useFullscreenStatus() @@ -49,14 +54,8 @@ export function TopMenuView() { View - { - // currently isFullscreen is a bit buggy and might not reflect the correct value - // setFullscreen(!isFullscreen) - - // so to be sure we use setFullscreen in "toggle" mode - // (ie. we don't pass a boolean, so it will act as a current value switch) setFullscreen() e.stopPropagation() @@ -64,11 +63,32 @@ export function TopMenuView() { return false }} > - Toggle fullscreen - - - +
+ + +
+
+ Toggle fullscreen + + + { setWindowLayout( windowLayout === UIWindowLayout.FLYING @@ -81,8 +101,34 @@ export function TopMenuView() { return false }} > - 🥭 Fruity mode (experimental) - +
+
+ + +
+
+ Toggle layout +
{/* diff --git a/src/components/windows/index.tsx b/src/components/windows/index.tsx index cbd5eb46..57c6594a 100644 --- a/src/components/windows/index.tsx +++ b/src/components/windows/index.tsx @@ -1,4 +1,11 @@ -import React, { useState, useCallback, useRef, useEffect } from 'react' +import React, { + useState, + useCallback, + useRef, + useEffect, + useMemo, + memo, +} from 'react' import { IoClose } from 'react-icons/io5' import { LuPanelTopClose, LuPanelTopOpen } from 'react-icons/lu' @@ -7,6 +14,7 @@ import { useTheme } from '@/services' import { useWindows } from '@/services/windows/useWindows' import { useFullscreenStatus } from '@/lib/hooks' import { RiFullscreenFill } from 'react-icons/ri' +import { isValidNumber } from '@aitube/clap' // FruityDesktop component export const FruityDesktop: React.FC<{ @@ -29,7 +37,6 @@ export const FruityDesktop: React.FC<{ ) } -// FruityWindow component export const FruityWindow: React.FC<{ id: string title?: string | JSX.Element @@ -44,372 +51,393 @@ export const FruityWindow: React.FC<{ canBeFullScreen?: boolean toolbar?: (props: { isFocused: boolean }) => JSX.Element children?: React.ReactNode -}> = ({ - id, - title = 'Untitled', - defaultWidth = 800, - minWidth = 160, - defaultHeight = 600, - minHeight = 100, - defaultX, - defaultY, - canBeReduced = true, - canBeClosed = true, - canBeFullScreen = true, - toolbar, - children, -}) => { - const theme = useTheme() - const windowRef = useRef(null) - const headerRef = useRef(null) - const [isEditing, setIsEditing] = useState(false) - const [isDragging, setIsDragging] = useState(false) - const [isResizing, setIsResizing] = useState(false) - const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 }) - const [resizeDirection, setResizeDirection] = useState('') - - const window = useWindows(useCallback((state) => state.windows[id], [id])) - const addWindow = useWindows((state) => state.addWindow) - const updateWindow = useWindows((state) => state.updateWindow) - const focusWindow = useWindows((state) => state.focusWindow) - const removeWindow = useWindows((state) => state.removeWindow) - - const [isFullscreen, setFullscreen, ref] = useFullscreenStatus() - - const parseSize = (size: number | string): number => { - if (typeof size === 'number') return size - if (typeof size === 'string') { - const parsed = parseInt(size, 10) - return isNaN(parsed) ? 0 : parsed - } - return 0 - } - - useEffect(() => { - if (!window) { - addWindow({ - id, - title, - isVisible: true, - width: parseSize(defaultWidth), - height: parseSize(defaultHeight), - x: defaultX, - y: defaultY, - canBeReduced, - canBeClosed, - }) - } - }, [ - addWindow, - canBeClosed, - canBeReduced, - defaultHeight, - defaultWidth, +}> = memo( + ({ + id, + title = 'Untitled', + defaultWidth = 800, + minWidth = 160, + defaultHeight = 600, + minHeight = 100, defaultX, defaultY, - id, - title, - window, - ]) + canBeReduced = true, + canBeClosed = true, + canBeFullScreen = true, + toolbar, + children, + }) => { + const theme = useTheme() + const windowRef = useRef(null) + const headerRef = useRef(null) + const [isEditing, setIsEditing] = useState(false) + const [isDragging, setIsDragging] = useState(false) + const [isResizing, setIsResizing] = useState(false) + const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 }) + const [resizeDirection, setResizeDirection] = useState('') - useEffect(() => { - const handleMouseDown = (e: MouseEvent) => { - if (windowRef.current && windowRef.current.contains(e.target as Node)) { - focusWindow(id) - } - } + const window = useWindows(useCallback((state) => state.windows[id], [id])) + const addWindow = useWindows((state) => state.addWindow) + const updateWindow = useWindows((state) => state.updateWindow) + const focusWindow = useWindows((state) => state.focusWindow) + const removeWindow = useWindows((state) => state.removeWindow) - document.addEventListener('mousedown', handleMouseDown) - return () => { - document.removeEventListener('mousedown', handleMouseDown) - } - }, [focusWindow, id]) + const [isFullscreen, setFullscreen, ref] = useFullscreenStatus() - const handleDragStart = useCallback( - (e: React.MouseEvent) => { - if (window) { - setIsDragging(true) - setDragOffset({ - x: e.clientX - window.x, - y: e.clientY - window.y, - }) + const parseSize = (size: number | string): number => { + if (typeof size === 'number') return size + if (typeof size === 'string') { + const parsed = parseInt(size, 10) + return isNaN(parsed) ? 0 : parsed } - }, - [window] - ) - - const handleDrag = useCallback( - (e: MouseEvent) => { - if (isDragging && window) { - const newY = Math.max(0, e.clientY - dragOffset.y) // Ensure y is at least 32px from the top - updateWindow(id, { - x: e.clientX - dragOffset.x, - y: newY, - }) - } - }, - [dragOffset.x, dragOffset.y, id, isDragging, updateWindow, window] - ) - const handleDragEnd = useCallback(() => { - setIsDragging(false) - }, []) - - useEffect(() => { - if (isDragging) { - document.addEventListener('mousemove', handleDrag) - document.addEventListener('mouseup', handleDragEnd) - } else { - document.removeEventListener('mousemove', handleDrag) - document.removeEventListener('mouseup', handleDragEnd) + return 0 } - return () => { - document.removeEventListener('mousemove', handleDrag) - document.removeEventListener('mouseup', handleDragEnd) - } - }, [isDragging, handleDrag, handleDragEnd]) - const handleResizeStart = useCallback( - (e: React.MouseEvent, direction: string) => { - e.preventDefault() - e.stopPropagation() - if (window) { - setIsResizing(true) - setResizeDirection(direction) - setDragOffset({ - x: e.clientX, - y: e.clientY, + useEffect(() => { + if (!window) { + addWindow({ + id, + title, + isVisible: true, + width: parseSize(defaultWidth), + height: parseSize(defaultHeight), + x: defaultX, + y: defaultY, + canBeReduced, + canBeClosed, }) } - }, - [window] - ) + }, [ + addWindow, + canBeClosed, + canBeReduced, + defaultHeight, + defaultWidth, + defaultX, + defaultY, + id, + title, + window, + ]) - const handleResize = useCallback( - (e: MouseEvent) => { - if (isResizing && window) { - const dx = e.clientX - dragOffset.x - const dy = e.clientY - dragOffset.y - let newWidth = window.width - let newHeight = window.height - let newX = window.x - let newY = window.y + useEffect(() => { + const handleMouseDown = (e: MouseEvent) => { + if (windowRef.current && windowRef.current.contains(e.target as Node)) { + focusWindow(id) + } + } - const parsedMinWidth = parseSize(minWidth) - const parsedMinHeight = parseSize(minHeight) + document.addEventListener('mousedown', handleMouseDown) + return () => { + document.removeEventListener('mousedown', handleMouseDown) + } + }, [focusWindow, id]) - if (resizeDirection.includes('w')) { - newWidth = Math.max(parsedMinWidth, window.width - dx) - newX = window.x + window.width - newWidth + const handleDragStart = useCallback( + (e: React.MouseEvent) => { + if (isValidNumber(window?.x) && isValidNumber(window?.y)) { + setIsDragging(true) + setDragOffset({ + x: e.clientX - window.x, + y: e.clientY - window.y, + }) } - if (resizeDirection.includes('e')) { - newWidth = Math.max(parsedMinWidth, window.width + dx) - } - if (resizeDirection.includes('n')) { - newHeight = Math.max(parsedMinHeight, window.height - dy) - newY = Math.max(0, window.y + window.height - newHeight) - newHeight = window.y + window.height - newY // Adjust height based on the new Y position + }, + [window?.x, window?.y, setIsDragging, setDragOffset] + ) + + const handleDrag = useCallback( + (e: MouseEvent) => { + if (isDragging && window) { + const newY = Math.max(0, e.clientY - dragOffset.y) // Ensure y is at least 32px from the top + updateWindow(id, { + x: e.clientX - dragOffset.x, + y: newY, + }) } - if (resizeDirection.includes('s')) { - newHeight = Math.max(parsedMinHeight, window.height + dy) + }, + [dragOffset.x, dragOffset.y, id, isDragging, updateWindow, window] + ) + const handleDragEnd = useCallback(() => { + setIsDragging(false) + }, []) + + useEffect(() => { + if (isDragging) { + document.addEventListener('mousemove', handleDrag) + document.addEventListener('mouseup', handleDragEnd) + } else { + document.removeEventListener('mousemove', handleDrag) + document.removeEventListener('mouseup', handleDragEnd) + } + return () => { + document.removeEventListener('mousemove', handleDrag) + document.removeEventListener('mouseup', handleDragEnd) + } + }, [isDragging, handleDrag, handleDragEnd]) + + const handleResizeStart = useCallback( + (e: React.MouseEvent, direction: string) => { + e.preventDefault() + e.stopPropagation() + if (window) { + setIsResizing(true) + setResizeDirection(direction) + setDragOffset({ + x: e.clientX, + y: e.clientY, + }) } + }, + [window, setIsResizing, setResizeDirection, setDragOffset] + ) + + const handleResize = useCallback( + (e: MouseEvent) => { + if (isResizing && window) { + const dx = e.clientX - dragOffset.x + const dy = e.clientY - dragOffset.y + let newWidth = window.width + let newHeight = window.height + let newX = window.x + let newY = window.y + + const parsedMinWidth = parseSize(minWidth) + const parsedMinHeight = parseSize(minHeight) - // Ensure the window doesn't go above the limit - if (newY < 0) { - newHeight = newHeight - (0 - newY) - newY = 0 + if (resizeDirection.includes('w')) { + newWidth = Math.max(parsedMinWidth, window.width - dx) + newX = window.x + window.width - newWidth + } + if (resizeDirection.includes('e')) { + newWidth = Math.max(parsedMinWidth, window.width + dx) + } + if (resizeDirection.includes('n')) { + newHeight = Math.max(parsedMinHeight, window.height - dy) + newY = Math.max(0, window.y + window.height - newHeight) + newHeight = window.y + window.height - newY // Adjust height based on the new Y position + } + if (resizeDirection.includes('s')) { + newHeight = Math.max(parsedMinHeight, window.height + dy) + } + + // Ensure the window doesn't go above the limit + if (newY < 0) { + newHeight = newHeight - (0 - newY) + newY = 0 + } + + updateWindow(id, { + width: newWidth, + height: newHeight, + x: newX, + y: newY, + }) + setDragOffset({ x: e.clientX, y: e.clientY }) } + }, + [ + dragOffset.x, + dragOffset.y, + id, + isResizing, + minHeight, + minWidth, + resizeDirection, + updateWindow, + window, + ] + ) - updateWindow(id, { - width: newWidth, - height: newHeight, - x: newX, - y: newY, - }) - setDragOffset({ x: e.clientX, y: e.clientY }) + const handleResizeEnd = useCallback(() => { + setIsResizing(false) + setResizeDirection('') + }, []) + + useEffect(() => { + if (isResizing) { + document.addEventListener('mousemove', handleResize) + document.addEventListener('mouseup', handleResizeEnd) + } else { + document.removeEventListener('mousemove', handleResize) + document.removeEventListener('mouseup', handleResizeEnd) } - }, - [ - dragOffset.x, - dragOffset.y, - id, - isResizing, - minHeight, - minWidth, - resizeDirection, - updateWindow, - window, - ] - ) + return () => { + document.removeEventListener('mousemove', handleResize) + document.removeEventListener('mouseup', handleResizeEnd) + } + }, [isResizing, handleResize, handleResizeEnd]) - const handleResizeEnd = useCallback(() => { - setIsResizing(false) - setResizeDirection('') - }, []) + const toggleReduce = useCallback(() => { + if (window) { + updateWindow(id, { isReduced: !window.isReduced }) + } + }, [id, updateWindow, window]) - useEffect(() => { - if (isResizing) { - document.addEventListener('mousemove', handleResize) - document.addEventListener('mouseup', handleResizeEnd) - } else { - document.removeEventListener('mousemove', handleResize) - document.removeEventListener('mouseup', handleResizeEnd) - } - return () => { - document.removeEventListener('mousemove', handleResize) - document.removeEventListener('mouseup', handleResizeEnd) - } - }, [isResizing, handleResize, handleResizeEnd]) + const handleHeaderDoubleClick = useCallback( + (e: React.MouseEvent) => { + // Check if the double-click occurred on the header background + if (e.target === headerRef.current) { + toggleReduce() + } + }, + [toggleReduce] + ) - const toggleReduce = useCallback(() => { - if (window) { - updateWindow(id, { isReduced: !window.isReduced }) - } - }, [id, updateWindow, window]) + const windowStyle = useMemo( + () => + window + ? { + width: isFullscreen ? '100vw' : `${window?.width}px`, + height: isFullscreen + ? '100vh' + : window.isReduced + ? 'auto' + : `${window.height}px`, + transform: isFullscreen + ? 'none' + : `translate(${window.x}px, ${window.y}px)`, + zIndex: window.zIndex, + backgroundColor: theme.editorBgColor || 'rgb(38, 38, 38)', + borderColor: + theme.windowBorderColor || + theme.editorBorderColor || + 'rgb(64, 64, 64)', + borderRadius: isFullscreen + ? '0' + : theme.windowBorderRadius || '8px', + } + : {}, + [isFullscreen, window, theme] + ) - const handleHeaderDoubleClick = useCallback( - (e: React.MouseEvent) => { - // Check if the double-click occurred on the header background - if (e.target === headerRef.current) { - toggleReduce() - } - }, - [toggleReduce] - ) + const windowClassName = useMemo( + () => + window + ? cn( + `absolute overflow-hidden shadow-lg`, + `border border-white/5`, + window.isFocused ? 'shadow-xl' : '', + isFullscreen ? 'fixed inset-0' : '' + ) + : 'display-none', + [window, window?.isFocused, isFullscreen] + ) - if (!window) return null + if (!window) return null - return ( -
- {!isFullscreen && ( -
- {isEditing ? ( - updateWindow(id, { title: e.target.value })} - onBlur={() => setIsEditing(false)} - onKeyDown={(e) => { - if (e.key === 'Enter') setIsEditing(false) - }} - className="rounded-none bg-neutral-950/80 px-0 text-sm text-white/60" - autoFocus - /> - ) : ( -
setIsEditing(true)}> - {typeof window.title === 'string' - ? window.title - : (window.title as any)({ isFocused: window.isFocused })} -
- )} -
- {toolbar && toolbar({ isFocused: window.isFocused })} - {canBeFullScreen && ( - - )} - {canBeReduced && ( - + return ( +
+ {!isFullscreen && ( +
removeWindow(id)} - className="text-white/60 hover:text-white/80" + onMouseDown={handleDragStart} + onDoubleClick={handleHeaderDoubleClick} + style={{ + backgroundColor: theme.editorMenuBgColor || 'rgb(38, 38, 38)', + }} + > + {isEditing ? ( + updateWindow(id, { title: e.target.value })} + onBlur={() => setIsEditing(false)} + onKeyDown={(e) => { + if (e.key === 'Enter') setIsEditing(false) + }} + className="rounded-none bg-neutral-950/80 px-0 text-sm text-white/60" + autoFocus + /> + ) : ( +
setIsEditing(true)} > - - + {typeof window.title === 'string' + ? window.title + : (window.title as any)({ isFocused: window.isFocused })} +
)} +
+ {toolbar && toolbar({ isFocused: window.isFocused })} + {canBeFullScreen && ( + + )} + {canBeReduced && ( + + )} + {canBeClosed && ( + + )} +
-
- )} - {!window.isReduced && ( -
- {children} -
- )} - {!isFullscreen && !window.isReduced && ( - <> + )} + {!window.isReduced && (
handleResizeStart(e, 'w')} - /> -
handleResizeStart(e, 'e')} - /> -
handleResizeStart(e, 'n')} - /> -
handleResizeStart(e, 's')} - /> -
handleResizeStart(e, 'nw')} - /> -
handleResizeStart(e, 'ne')} - /> -
handleResizeStart(e, 'sw')} - /> -
handleResizeStart(e, 'se')} - /> - - )} -
- ) -} + ref={ref as any} + className={isFullscreen ? 'h-full w-full' : 'h-[calc(100%-32px)]'} + > + {children} +
+ )} + {!isFullscreen && !window.isReduced && ( + <> +
handleResizeStart(e, 'w')} + /> +
handleResizeStart(e, 'e')} + /> +
handleResizeStart(e, 'n')} + /> +
handleResizeStart(e, 's')} + /> +
handleResizeStart(e, 'nw')} + /> +
handleResizeStart(e, 'ne')} + /> +
handleResizeStart(e, 'sw')} + /> +
handleResizeStart(e, 'se')} + /> + + )} +
+ ) + } +) + +FruityWindow.displayName = 'FruityWindow' diff --git a/src/lib/core/constants.ts b/src/lib/core/constants.ts index 1da1978b..ce407883 100644 --- a/src/lib/core/constants.ts +++ b/src/lib/core/constants.ts @@ -3,7 +3,7 @@ export const HARD_LIMIT_NB_MAX_ASSETS_TO_GENERATE_IN_PARALLEL = 32 export const APP_NAME = 'Clapper.app' -export const APP_REVISION = '20240729+1107' +export const APP_REVISION = '20240729+1441' export const APP_DOMAIN = 'Clapper.app' export const APP_LINK = 'https://clapper.app' diff --git a/src/lib/hooks/useFullscreenStatus.ts b/src/lib/hooks/useFullscreenStatus.ts index ba1b0363..500d34db 100644 --- a/src/lib/hooks/useFullscreenStatus.ts +++ b/src/lib/hooks/useFullscreenStatus.ts @@ -2,17 +2,17 @@ import React, { useState, - useLayoutEffect, + useEffect, useRef, MutableRefObject, useCallback, } from 'react' interface FullscreenElement { - fullscreenElement?: Element - mozFullScreenElement?: Element - msFullscreenElement?: Element - webkitFullscreenElement?: Element + fullscreenElement?: Element | null + mozFullScreenElement?: Element | null + msFullscreenElement?: Element | null + webkitFullscreenElement?: Element | null } declare global { @@ -21,66 +21,68 @@ declare global { export function useFullscreenStatus(): [ boolean, - (requestedValue?: boolean) => void, + (requestedValue?: boolean) => Promise, MutableRefObject, ] { const elRef = useRef(null) const [isFullscreen, setIsFullscreen] = useState(false) - const getFullscreenStatus = useCallback(() => { - return typeof document !== 'undefined' - ? Boolean( - (document as FullscreenElement)[getBrowserFullscreenElementProp()] - ) - : false + const getFullscreenElement = useCallback((): Element | null => { + if (typeof document === 'undefined') return null + const fullscreenProp = getBrowserFullscreenElementProp() + return document[fullscreenProp] || null }, []) const updateFullscreenStatus = useCallback(() => { - setIsFullscreen(getFullscreenStatus()) - }, [getFullscreenStatus]) + const fullscreenElement = getFullscreenElement() + setIsFullscreen( + !!elRef.current && + !!fullscreenElement && + fullscreenElement === elRef.current + ) + }, [getFullscreenElement]) const setFullscreen = useCallback( - (maybeValue?: boolean) => { + async (requestedValue?: boolean) => { if (!elRef.current) return - const isFullScreen = getFullscreenStatus() - const shouldBeFullScreen = - typeof maybeValue === 'boolean' ? maybeValue : !isFullScreen + const isCurrentlyFullscreen = getFullscreenElement() === elRef.current + const shouldBeFullScreen = requestedValue ?? !isCurrentlyFullscreen - if (isFullScreen === shouldBeFullScreen) { - return - } + if (isCurrentlyFullscreen === shouldBeFullScreen) return - const operation = shouldBeFullScreen - ? elRef.current.requestFullscreen() - : typeof document !== 'undefined' - ? document.exitFullscreen() - : Promise.resolve(true) + try { + if (shouldBeFullScreen) { + await elRef.current.requestFullscreen() + } else if (document.fullscreenElement) { + await document.exitFullscreen() + } + } catch (error) { + console.error('Fullscreen error:', error) + } - operation.then(updateFullscreenStatus).catch(updateFullscreenStatus) + // Wait for the next frame before updating status + requestAnimationFrame(updateFullscreenStatus) }, - [getFullscreenStatus, updateFullscreenStatus] + [getFullscreenElement, updateFullscreenStatus] ) - useLayoutEffect(() => { - updateFullscreenStatus() - + useEffect(() => { const fullscreenChangeHandler = () => { - updateFullscreenStatus() + requestAnimationFrame(updateFullscreenStatus) } if (typeof document !== 'undefined') { document.addEventListener('fullscreenchange', fullscreenChangeHandler) - // Polling mechanism - const intervalId = setInterval(updateFullscreenStatus, 100) + // Initial status update + updateFullscreenStatus() return () => { document.removeEventListener( 'fullscreenchange', fullscreenChangeHandler ) - clearInterval(intervalId) } } }, [updateFullscreenStatus]) @@ -89,15 +91,20 @@ export function useFullscreenStatus(): [ } function getBrowserFullscreenElementProp(): keyof FullscreenElement { - if (typeof document.fullscreenElement !== 'undefined') { - return 'fullscreenElement' - } else if (typeof document.mozFullScreenElement !== 'undefined') { - return 'mozFullScreenElement' - } else if (typeof document.msFullscreenElement !== 'undefined') { - return 'msFullscreenElement' - } else if (typeof document.webkitFullscreenElement !== 'undefined') { - return 'webkitFullscreenElement' - } else { - throw new Error('fullscreenElement is not supported by this browser') + if (typeof document === 'undefined') return 'fullscreenElement' + + const props: (keyof FullscreenElement)[] = [ + 'fullscreenElement', + 'mozFullScreenElement', + 'msFullscreenElement', + 'webkitFullscreenElement', + ] + + for (const prop of props) { + if (prop in document) { + return prop + } } + + return 'fullscreenElement' } diff --git a/src/services/windows/useWindows.ts b/src/services/windows/useWindows.ts index a7b34668..3884dbd1 100644 --- a/src/services/windows/useWindows.ts +++ b/src/services/windows/useWindows.ts @@ -3,13 +3,12 @@ import { create } from 'zustand' import { WindowsStore, WindowState } from './types' import { useCallback } from 'react' -// Create the Zustand store export const useWindows = create((set, get) => ({ windows: {}, getNextPosition: (width: number, height: number) => { const state = get() const existingWindows = Object.values(state.windows) - const defaultOffset = 30 // Offset for cascading windows + const defaultOffset = 100 // Offset for cascading windows let newX = 0 let newY = 0 let maxIterations = 100 // Prevent infinite loop @@ -73,7 +72,9 @@ export const useWindows = create((set, get) => ({ set((state) => ({ windows: { ...state.windows, - [id]: { ...state.windows[id], ...updates }, + [id]: state.windows[id] + ? { ...state.windows[id], ...updates } + : state.windows[id], }, })), removeWindow: (id) =>