jbilcke-hf HF staff commited on
Commit
fd46ec1
β€’
1 Parent(s): 51ead01

improve window layout

Browse files
package-lock.json CHANGED
@@ -11,9 +11,9 @@
11
  "dependencies": {
12
  "@aitube/broadway": "0.1.2",
13
  "@aitube/clap": "0.1.2",
14
- "@aitube/clapper-services": "0.1.2-7",
15
  "@aitube/engine": "0.1.2",
16
- "@aitube/timeline": "0.1.2-1",
17
  "@fal-ai/serverless-client": "^0.13.0",
18
  "@ffmpeg/ffmpeg": "^0.12.10",
19
  "@ffmpeg/util": "^0.12.1",
@@ -184,12 +184,12 @@
184
  }
185
  },
186
  "node_modules/@aitube/clapper-services": {
187
- "version": "0.1.2-7",
188
- "resolved": "https://registry.npmjs.org/@aitube/clapper-services/-/clapper-services-0.1.2-7.tgz",
189
- "integrity": "sha512-xqt8XG8R7SHG+7bBb1+5yHzbfkpndyGiJOV8Agz5wfcuOKHuSk8EOrcKc7oB2t/AVrnkQSp9ph+6Gnf1mOXLpg==",
190
  "peerDependencies": {
191
  "@aitube/clap": "0.1.2",
192
- "@aitube/timeline": "0.1.2-1",
193
  "@monaco-editor/react": "4.6.0",
194
  "monaco-editor": "0.50.0",
195
  "react": "*",
@@ -215,9 +215,9 @@
215
  }
216
  },
217
  "node_modules/@aitube/timeline": {
218
- "version": "0.1.2-1",
219
- "resolved": "https://registry.npmjs.org/@aitube/timeline/-/timeline-0.1.2-1.tgz",
220
- "integrity": "sha512-7Y4ExWf2GA2p7a0W8Yt6pfIEa79I0oXiyhGWY+b2BN0SXKHOwEYYnlNlDBQhTDILsHsYgKnvvABt2iQdnt4xxA==",
221
  "dependencies": {
222
  "date-fns": "^3.6.0",
223
  "react-virtualized-auto-sizer": "^1.0.24"
@@ -1547,9 +1547,9 @@
1547
  }
1548
  },
1549
  "node_modules/@babel/traverse": {
1550
- "version": "7.25.0",
1551
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.0.tgz",
1552
- "integrity": "sha512-ubALThHQy4GCf6mbb+5ZRNmLLCI7bJ3f8Q6LHBSRlSKSWj5a7dSUzJBLv3VuIhFrFPgjF4IzPF567YG/HSCdZA==",
1553
  "dependencies": {
1554
  "@babel/code-frame": "^7.24.7",
1555
  "@babel/generator": "^7.25.0",
@@ -2806,6 +2806,15 @@
2806
  "node": ">=14.14"
2807
  }
2808
  },
 
 
 
 
 
 
 
 
 
2809
  "node_modules/@emotion/is-prop-valid": {
2810
  "version": "1.3.0",
2811
  "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.0.tgz",
@@ -2829,6 +2838,70 @@
2829
  "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
2830
  "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg=="
2831
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2832
  "node_modules/@esbuild/darwin-arm64": {
2833
  "version": "0.21.5",
2834
  "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
@@ -2845,6 +2918,294 @@
2845
  "node": ">=12"
2846
  }
2847
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2848
  "node_modules/@eslint-community/eslint-utils": {
2849
  "version": "4.4.0",
2850
  "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@@ -3238,6 +3599,27 @@
3238
  "url": "https://opencollective.com/libvips"
3239
  }
3240
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3241
  "node_modules/@img/sharp-libvips-linux-x64": {
3242
  "version": "1.0.2",
3243
  "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.2.tgz",
@@ -3369,6 +3751,31 @@
3369
  "@img/sharp-libvips-linux-arm64": "1.0.2"
3370
  }
3371
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3372
  "node_modules/@img/sharp-linux-x64": {
3373
  "version": "0.33.4",
3374
  "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.4.tgz",
@@ -3444,6 +3851,27 @@
3444
  "@img/sharp-libvips-linuxmusl-x64": "1.0.2"
3445
  }
3446
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3447
  "node_modules/@img/sharp-win32-ia32": {
3448
  "version": "0.33.4",
3449
  "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.4.tgz",
@@ -3487,11 +3915,11 @@
3487
  }
3488
  },
3489
  "node_modules/@inquirer/confirm": {
3490
- "version": "3.1.17",
3491
- "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-3.1.17.tgz",
3492
- "integrity": "sha512-qCpt/AABzPynz8tr69VDvhcjwmzAryipWXtW8Vi6m651da4H/d0Bdn55LkxXD7Rp2gfgxvxzTdb66AhIA8gzBA==",
3493
  "dependencies": {
3494
- "@inquirer/core": "^9.0.5",
3495
  "@inquirer/type": "^1.5.1"
3496
  },
3497
  "engines": {
@@ -3499,14 +3927,14 @@
3499
  }
3500
  },
3501
  "node_modules/@inquirer/core": {
3502
- "version": "9.0.5",
3503
- "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-9.0.5.tgz",
3504
- "integrity": "sha512-QWG41I7vn62O9stYKg/juKXt1PEbr/4ZZCPb4KgXDQGwgA9M5NBTQ7FnOvT1ridbxkm/wTxLCNraUs7y47pIRQ==",
3505
  "dependencies": {
3506
  "@inquirer/figures": "^1.0.5",
3507
  "@inquirer/type": "^1.5.1",
3508
  "@types/mute-stream": "^0.0.4",
3509
- "@types/node": "^20.14.11",
3510
  "@types/wrap-ansi": "^3.0.0",
3511
  "ansi-escapes": "^4.3.2",
3512
  "cli-spinners": "^2.9.2",
@@ -6108,6 +6536,32 @@
6108
  "url": "https://github.com/chalk/strip-ansi?sponsor=1"
6109
  }
6110
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6111
  "node_modules/@rollup/rollup-darwin-arm64": {
6112
  "version": "4.19.1",
6113
  "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.19.1.tgz",
@@ -6121,6 +6575,175 @@
6121
  "darwin"
6122
  ]
6123
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6124
  "node_modules/@rushstack/eslint-patch": {
6125
  "version": "1.10.4",
6126
  "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.4.tgz",
@@ -7023,9 +7646,9 @@
7023
  }
7024
  },
7025
  "node_modules/@types/node": {
7026
- "version": "20.14.12",
7027
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.12.tgz",
7028
- "integrity": "sha512-r7wNXakLeSsGT0H1AU863vS2wa5wBOK4bWMjZz2wj+8nBx+m5PeIn0k8AloSLpRuiwdRQZwarZqHE4FNArPuJQ==",
7029
  "dependencies": {
7030
  "undici-types": "~5.26.4"
7031
  }
@@ -7399,7 +8022,7 @@
7399
  },
7400
  "node_modules/@xenova/transformers": {
7401
  "version": "3.0.0-alpha.0",
7402
- "resolved": "git+ssh://git@github.com/xenova/transformers.js.git#c6aeb4be1bc1cdfa72e9d050f77b97dc9c8af362",
7403
  "dependencies": {
7404
  "@huggingface/jinja": "^0.2.2",
7405
  "onnxruntime-web": "^1.18.0",
@@ -9812,9 +10435,9 @@
9812
  }
9813
  },
9814
  "node_modules/detect-gpu": {
9815
- "version": "5.0.40",
9816
- "resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.40.tgz",
9817
- "integrity": "sha512-5v4jDN/ERdZZitD29UiLjV9Q9+lDfw2OhEJACIqnvdWulVZCy2K6EwonZ/VKyo4YMqvSIzGIDmojX3jGL3dLpA==",
9818
  "dependencies": {
9819
  "webgl-constants": "^1.1.1"
9820
  }
@@ -11637,9 +12260,9 @@
11637
  }
11638
  },
11639
  "node_modules/fast-xml-parser": {
11640
- "version": "4.4.0",
11641
- "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.0.tgz",
11642
- "integrity": "sha512-kLY3jFlwIYwBNDojclKsNAC12sfD6NwW74QB2CoNGPvtVxjliYehVunB3HYyNi+n4Tt1dAcgwYvmKF/Z18flqg==",
11643
  "funding": [
11644
  {
11645
  "type": "github",
@@ -20295,9 +20918,9 @@
20295
  }
20296
  },
20297
  "node_modules/zod-to-json-schema": {
20298
- "version": "3.23.1",
20299
- "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.23.1.tgz",
20300
- "integrity": "sha512-oT9INvydob1XV0v1d2IadrR74rLtDInLvDFfAa1CG0Pmg/vxATk7I2gSelfj271mbzeM4Da0uuDQE/Nkj3DWNw==",
20301
  "peerDependencies": {
20302
  "zod": "^3.23.3"
20303
  }
 
11
  "dependencies": {
12
  "@aitube/broadway": "0.1.2",
13
  "@aitube/clap": "0.1.2",
14
+ "@aitube/clapper-services": "0.1.2-8",
15
  "@aitube/engine": "0.1.2",
16
+ "@aitube/timeline": "0.1.2-2",
17
  "@fal-ai/serverless-client": "^0.13.0",
18
  "@ffmpeg/ffmpeg": "^0.12.10",
19
  "@ffmpeg/util": "^0.12.1",
 
184
  }
185
  },
186
  "node_modules/@aitube/clapper-services": {
187
+ "version": "0.1.2-8",
188
+ "resolved": "https://registry.npmjs.org/@aitube/clapper-services/-/clapper-services-0.1.2-8.tgz",
189
+ "integrity": "sha512-LjC9J8HZ/Dt94NzkEWRnm7v51d9Hncc/SjhzkMGEc6/FGnu97IhmPsI2DCI61a+SUlqYn14Nt+FdT949NHvVLw==",
190
  "peerDependencies": {
191
  "@aitube/clap": "0.1.2",
192
+ "@aitube/timeline": "0.1.2-2",
193
  "@monaco-editor/react": "4.6.0",
194
  "monaco-editor": "0.50.0",
195
  "react": "*",
 
215
  }
216
  },
217
  "node_modules/@aitube/timeline": {
218
+ "version": "0.1.2-2",
219
+ "resolved": "https://registry.npmjs.org/@aitube/timeline/-/timeline-0.1.2-2.tgz",
220
+ "integrity": "sha512-4ep73A0C7zEMreQ2jpCvJbCJJNQgQZBYxTQqJmoiZg5DPiIXFzkOTOYIJuhPu4wZX/Q14wPgpyXmF3UJ1x3ujA==",
221
  "dependencies": {
222
  "date-fns": "^3.6.0",
223
  "react-virtualized-auto-sizer": "^1.0.24"
 
1547
  }
1548
  },
1549
  "node_modules/@babel/traverse": {
1550
+ "version": "7.25.1",
1551
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.1.tgz",
1552
+ "integrity": "sha512-LrHHoWq08ZpmmFqBAzN+hUdWwy5zt7FGa/hVwMcOqW6OVtwqaoD5utfuGYU87JYxdZgLUvktAsn37j/sYR9siA==",
1553
  "dependencies": {
1554
  "@babel/code-frame": "^7.24.7",
1555
  "@babel/generator": "^7.25.0",
 
2806
  "node": ">=14.14"
2807
  }
2808
  },
2809
+ "node_modules/@emnapi/runtime": {
2810
+ "version": "1.2.0",
2811
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.2.0.tgz",
2812
+ "integrity": "sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==",
2813
+ "optional": true,
2814
+ "dependencies": {
2815
+ "tslib": "^2.4.0"
2816
+ }
2817
+ },
2818
  "node_modules/@emotion/is-prop-valid": {
2819
  "version": "1.3.0",
2820
  "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.0.tgz",
 
2838
  "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
2839
  "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg=="
2840
  },
2841
+ "node_modules/@esbuild/aix-ppc64": {
2842
+ "version": "0.21.5",
2843
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
2844
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
2845
+ "cpu": [
2846
+ "ppc64"
2847
+ ],
2848
+ "dev": true,
2849
+ "optional": true,
2850
+ "os": [
2851
+ "aix"
2852
+ ],
2853
+ "engines": {
2854
+ "node": ">=12"
2855
+ }
2856
+ },
2857
+ "node_modules/@esbuild/android-arm": {
2858
+ "version": "0.21.5",
2859
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
2860
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
2861
+ "cpu": [
2862
+ "arm"
2863
+ ],
2864
+ "dev": true,
2865
+ "optional": true,
2866
+ "os": [
2867
+ "android"
2868
+ ],
2869
+ "engines": {
2870
+ "node": ">=12"
2871
+ }
2872
+ },
2873
+ "node_modules/@esbuild/android-arm64": {
2874
+ "version": "0.21.5",
2875
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
2876
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
2877
+ "cpu": [
2878
+ "arm64"
2879
+ ],
2880
+ "dev": true,
2881
+ "optional": true,
2882
+ "os": [
2883
+ "android"
2884
+ ],
2885
+ "engines": {
2886
+ "node": ">=12"
2887
+ }
2888
+ },
2889
+ "node_modules/@esbuild/android-x64": {
2890
+ "version": "0.21.5",
2891
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
2892
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
2893
+ "cpu": [
2894
+ "x64"
2895
+ ],
2896
+ "dev": true,
2897
+ "optional": true,
2898
+ "os": [
2899
+ "android"
2900
+ ],
2901
+ "engines": {
2902
+ "node": ">=12"
2903
+ }
2904
+ },
2905
  "node_modules/@esbuild/darwin-arm64": {
2906
  "version": "0.21.5",
2907
  "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
 
2918
  "node": ">=12"
2919
  }
2920
  },
2921
+ "node_modules/@esbuild/darwin-x64": {
2922
+ "version": "0.21.5",
2923
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
2924
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
2925
+ "cpu": [
2926
+ "x64"
2927
+ ],
2928
+ "dev": true,
2929
+ "optional": true,
2930
+ "os": [
2931
+ "darwin"
2932
+ ],
2933
+ "engines": {
2934
+ "node": ">=12"
2935
+ }
2936
+ },
2937
+ "node_modules/@esbuild/freebsd-arm64": {
2938
+ "version": "0.21.5",
2939
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
2940
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
2941
+ "cpu": [
2942
+ "arm64"
2943
+ ],
2944
+ "dev": true,
2945
+ "optional": true,
2946
+ "os": [
2947
+ "freebsd"
2948
+ ],
2949
+ "engines": {
2950
+ "node": ">=12"
2951
+ }
2952
+ },
2953
+ "node_modules/@esbuild/freebsd-x64": {
2954
+ "version": "0.21.5",
2955
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
2956
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
2957
+ "cpu": [
2958
+ "x64"
2959
+ ],
2960
+ "dev": true,
2961
+ "optional": true,
2962
+ "os": [
2963
+ "freebsd"
2964
+ ],
2965
+ "engines": {
2966
+ "node": ">=12"
2967
+ }
2968
+ },
2969
+ "node_modules/@esbuild/linux-arm": {
2970
+ "version": "0.21.5",
2971
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
2972
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
2973
+ "cpu": [
2974
+ "arm"
2975
+ ],
2976
+ "dev": true,
2977
+ "optional": true,
2978
+ "os": [
2979
+ "linux"
2980
+ ],
2981
+ "engines": {
2982
+ "node": ">=12"
2983
+ }
2984
+ },
2985
+ "node_modules/@esbuild/linux-arm64": {
2986
+ "version": "0.21.5",
2987
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
2988
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
2989
+ "cpu": [
2990
+ "arm64"
2991
+ ],
2992
+ "dev": true,
2993
+ "optional": true,
2994
+ "os": [
2995
+ "linux"
2996
+ ],
2997
+ "engines": {
2998
+ "node": ">=12"
2999
+ }
3000
+ },
3001
+ "node_modules/@esbuild/linux-ia32": {
3002
+ "version": "0.21.5",
3003
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
3004
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
3005
+ "cpu": [
3006
+ "ia32"
3007
+ ],
3008
+ "dev": true,
3009
+ "optional": true,
3010
+ "os": [
3011
+ "linux"
3012
+ ],
3013
+ "engines": {
3014
+ "node": ">=12"
3015
+ }
3016
+ },
3017
+ "node_modules/@esbuild/linux-loong64": {
3018
+ "version": "0.21.5",
3019
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
3020
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
3021
+ "cpu": [
3022
+ "loong64"
3023
+ ],
3024
+ "dev": true,
3025
+ "optional": true,
3026
+ "os": [
3027
+ "linux"
3028
+ ],
3029
+ "engines": {
3030
+ "node": ">=12"
3031
+ }
3032
+ },
3033
+ "node_modules/@esbuild/linux-mips64el": {
3034
+ "version": "0.21.5",
3035
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
3036
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
3037
+ "cpu": [
3038
+ "mips64el"
3039
+ ],
3040
+ "dev": true,
3041
+ "optional": true,
3042
+ "os": [
3043
+ "linux"
3044
+ ],
3045
+ "engines": {
3046
+ "node": ">=12"
3047
+ }
3048
+ },
3049
+ "node_modules/@esbuild/linux-ppc64": {
3050
+ "version": "0.21.5",
3051
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
3052
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
3053
+ "cpu": [
3054
+ "ppc64"
3055
+ ],
3056
+ "dev": true,
3057
+ "optional": true,
3058
+ "os": [
3059
+ "linux"
3060
+ ],
3061
+ "engines": {
3062
+ "node": ">=12"
3063
+ }
3064
+ },
3065
+ "node_modules/@esbuild/linux-riscv64": {
3066
+ "version": "0.21.5",
3067
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
3068
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
3069
+ "cpu": [
3070
+ "riscv64"
3071
+ ],
3072
+ "dev": true,
3073
+ "optional": true,
3074
+ "os": [
3075
+ "linux"
3076
+ ],
3077
+ "engines": {
3078
+ "node": ">=12"
3079
+ }
3080
+ },
3081
+ "node_modules/@esbuild/linux-s390x": {
3082
+ "version": "0.21.5",
3083
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
3084
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
3085
+ "cpu": [
3086
+ "s390x"
3087
+ ],
3088
+ "dev": true,
3089
+ "optional": true,
3090
+ "os": [
3091
+ "linux"
3092
+ ],
3093
+ "engines": {
3094
+ "node": ">=12"
3095
+ }
3096
+ },
3097
+ "node_modules/@esbuild/linux-x64": {
3098
+ "version": "0.21.5",
3099
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
3100
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
3101
+ "cpu": [
3102
+ "x64"
3103
+ ],
3104
+ "dev": true,
3105
+ "optional": true,
3106
+ "os": [
3107
+ "linux"
3108
+ ],
3109
+ "engines": {
3110
+ "node": ">=12"
3111
+ }
3112
+ },
3113
+ "node_modules/@esbuild/netbsd-x64": {
3114
+ "version": "0.21.5",
3115
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
3116
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
3117
+ "cpu": [
3118
+ "x64"
3119
+ ],
3120
+ "dev": true,
3121
+ "optional": true,
3122
+ "os": [
3123
+ "netbsd"
3124
+ ],
3125
+ "engines": {
3126
+ "node": ">=12"
3127
+ }
3128
+ },
3129
+ "node_modules/@esbuild/openbsd-x64": {
3130
+ "version": "0.21.5",
3131
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
3132
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
3133
+ "cpu": [
3134
+ "x64"
3135
+ ],
3136
+ "dev": true,
3137
+ "optional": true,
3138
+ "os": [
3139
+ "openbsd"
3140
+ ],
3141
+ "engines": {
3142
+ "node": ">=12"
3143
+ }
3144
+ },
3145
+ "node_modules/@esbuild/sunos-x64": {
3146
+ "version": "0.21.5",
3147
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
3148
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
3149
+ "cpu": [
3150
+ "x64"
3151
+ ],
3152
+ "dev": true,
3153
+ "optional": true,
3154
+ "os": [
3155
+ "sunos"
3156
+ ],
3157
+ "engines": {
3158
+ "node": ">=12"
3159
+ }
3160
+ },
3161
+ "node_modules/@esbuild/win32-arm64": {
3162
+ "version": "0.21.5",
3163
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
3164
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
3165
+ "cpu": [
3166
+ "arm64"
3167
+ ],
3168
+ "dev": true,
3169
+ "optional": true,
3170
+ "os": [
3171
+ "win32"
3172
+ ],
3173
+ "engines": {
3174
+ "node": ">=12"
3175
+ }
3176
+ },
3177
+ "node_modules/@esbuild/win32-ia32": {
3178
+ "version": "0.21.5",
3179
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
3180
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
3181
+ "cpu": [
3182
+ "ia32"
3183
+ ],
3184
+ "dev": true,
3185
+ "optional": true,
3186
+ "os": [
3187
+ "win32"
3188
+ ],
3189
+ "engines": {
3190
+ "node": ">=12"
3191
+ }
3192
+ },
3193
+ "node_modules/@esbuild/win32-x64": {
3194
+ "version": "0.21.5",
3195
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
3196
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
3197
+ "cpu": [
3198
+ "x64"
3199
+ ],
3200
+ "dev": true,
3201
+ "optional": true,
3202
+ "os": [
3203
+ "win32"
3204
+ ],
3205
+ "engines": {
3206
+ "node": ">=12"
3207
+ }
3208
+ },
3209
  "node_modules/@eslint-community/eslint-utils": {
3210
  "version": "4.4.0",
3211
  "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
 
3599
  "url": "https://opencollective.com/libvips"
3600
  }
3601
  },
3602
+ "node_modules/@img/sharp-libvips-linux-s390x": {
3603
+ "version": "1.0.2",
3604
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.2.tgz",
3605
+ "integrity": "sha512-cmhQ1J4qVhfmS6szYW7RT+gLJq9dH2i4maq+qyXayUSn9/3iY2ZeWpbAgSpSVbV2E1JUL2Gg7pwnYQ1h8rQIog==",
3606
+ "cpu": [
3607
+ "s390x"
3608
+ ],
3609
+ "optional": true,
3610
+ "os": [
3611
+ "linux"
3612
+ ],
3613
+ "engines": {
3614
+ "glibc": ">=2.28",
3615
+ "npm": ">=9.6.5",
3616
+ "pnpm": ">=7.1.0",
3617
+ "yarn": ">=3.2.0"
3618
+ },
3619
+ "funding": {
3620
+ "url": "https://opencollective.com/libvips"
3621
+ }
3622
+ },
3623
  "node_modules/@img/sharp-libvips-linux-x64": {
3624
  "version": "1.0.2",
3625
  "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.2.tgz",
 
3751
  "@img/sharp-libvips-linux-arm64": "1.0.2"
3752
  }
3753
  },
3754
+ "node_modules/@img/sharp-linux-s390x": {
3755
+ "version": "0.33.4",
3756
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.4.tgz",
3757
+ "integrity": "sha512-h3RAL3siQoyzSoH36tUeS0PDmb5wINKGYzcLB5C6DIiAn2F3udeFAum+gj8IbA/82+8RGCTn7XW8WTFnqag4tQ==",
3758
+ "cpu": [
3759
+ "s390x"
3760
+ ],
3761
+ "optional": true,
3762
+ "os": [
3763
+ "linux"
3764
+ ],
3765
+ "engines": {
3766
+ "glibc": ">=2.31",
3767
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0",
3768
+ "npm": ">=9.6.5",
3769
+ "pnpm": ">=7.1.0",
3770
+ "yarn": ">=3.2.0"
3771
+ },
3772
+ "funding": {
3773
+ "url": "https://opencollective.com/libvips"
3774
+ },
3775
+ "optionalDependencies": {
3776
+ "@img/sharp-libvips-linux-s390x": "1.0.2"
3777
+ }
3778
+ },
3779
  "node_modules/@img/sharp-linux-x64": {
3780
  "version": "0.33.4",
3781
  "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.4.tgz",
 
3851
  "@img/sharp-libvips-linuxmusl-x64": "1.0.2"
3852
  }
3853
  },
3854
+ "node_modules/@img/sharp-wasm32": {
3855
+ "version": "0.33.4",
3856
+ "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.4.tgz",
3857
+ "integrity": "sha512-Bmmauh4sXUsUqkleQahpdNXKvo+wa1V9KhT2pDA4VJGKwnKMJXiSTGphn0gnJrlooda0QxCtXc6RX1XAU6hMnQ==",
3858
+ "cpu": [
3859
+ "wasm32"
3860
+ ],
3861
+ "optional": true,
3862
+ "dependencies": {
3863
+ "@emnapi/runtime": "^1.1.1"
3864
+ },
3865
+ "engines": {
3866
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0",
3867
+ "npm": ">=9.6.5",
3868
+ "pnpm": ">=7.1.0",
3869
+ "yarn": ">=3.2.0"
3870
+ },
3871
+ "funding": {
3872
+ "url": "https://opencollective.com/libvips"
3873
+ }
3874
+ },
3875
  "node_modules/@img/sharp-win32-ia32": {
3876
  "version": "0.33.4",
3877
  "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.4.tgz",
 
3915
  }
3916
  },
3917
  "node_modules/@inquirer/confirm": {
3918
+ "version": "3.1.18",
3919
+ "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-3.1.18.tgz",
3920
+ "integrity": "sha512-axDSeAtgRfMAOnI2NXJAcBliknRiPHBPBh8VpofFW2vSt5nxU/IoNcWfNBIs1LFwICyLzbvGjF3fd+rYLSU11w==",
3921
  "dependencies": {
3922
+ "@inquirer/core": "^9.0.6",
3923
  "@inquirer/type": "^1.5.1"
3924
  },
3925
  "engines": {
 
3927
  }
3928
  },
3929
  "node_modules/@inquirer/core": {
3930
+ "version": "9.0.6",
3931
+ "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-9.0.6.tgz",
3932
+ "integrity": "sha512-pmwIJJrtOBmP29JLPkdq5ORGGaSzOwZbashYyME20sD5AITiy2j3LFsnTXXuiqPIkq4XjQYOHzaExAmqjyU1Cg==",
3933
  "dependencies": {
3934
  "@inquirer/figures": "^1.0.5",
3935
  "@inquirer/type": "^1.5.1",
3936
  "@types/mute-stream": "^0.0.4",
3937
+ "@types/node": "^20.14.13",
3938
  "@types/wrap-ansi": "^3.0.0",
3939
  "ansi-escapes": "^4.3.2",
3940
  "cli-spinners": "^2.9.2",
 
6536
  "url": "https://github.com/chalk/strip-ansi?sponsor=1"
6537
  }
6538
  },
6539
+ "node_modules/@rollup/rollup-android-arm-eabi": {
6540
+ "version": "4.19.1",
6541
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.19.1.tgz",
6542
+ "integrity": "sha512-XzqSg714++M+FXhHfXpS1tDnNZNpgxxuGZWlRG/jSj+VEPmZ0yg6jV4E0AL3uyBKxO8mO3xtOsP5mQ+XLfrlww==",
6543
+ "cpu": [
6544
+ "arm"
6545
+ ],
6546
+ "dev": true,
6547
+ "optional": true,
6548
+ "os": [
6549
+ "android"
6550
+ ]
6551
+ },
6552
+ "node_modules/@rollup/rollup-android-arm64": {
6553
+ "version": "4.19.1",
6554
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.19.1.tgz",
6555
+ "integrity": "sha512-thFUbkHteM20BGShD6P08aungq4irbIZKUNbG70LN8RkO7YztcGPiKTTGZS7Kw+x5h8hOXs0i4OaHwFxlpQN6A==",
6556
+ "cpu": [
6557
+ "arm64"
6558
+ ],
6559
+ "dev": true,
6560
+ "optional": true,
6561
+ "os": [
6562
+ "android"
6563
+ ]
6564
+ },
6565
  "node_modules/@rollup/rollup-darwin-arm64": {
6566
  "version": "4.19.1",
6567
  "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.19.1.tgz",
 
6575
  "darwin"
6576
  ]
6577
  },
6578
+ "node_modules/@rollup/rollup-darwin-x64": {
6579
+ "version": "4.19.1",
6580
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.19.1.tgz",
6581
+ "integrity": "sha512-4T42heKsnbjkn7ovYiAdDVRRWZLU9Kmhdt6HafZxFcUdpjlBlxj4wDrt1yFWLk7G4+E+8p2C9tcmSu0KA6auGA==",
6582
+ "cpu": [
6583
+ "x64"
6584
+ ],
6585
+ "dev": true,
6586
+ "optional": true,
6587
+ "os": [
6588
+ "darwin"
6589
+ ]
6590
+ },
6591
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
6592
+ "version": "4.19.1",
6593
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.19.1.tgz",
6594
+ "integrity": "sha512-MXg1xp+e5GhZ3Vit1gGEyoC+dyQUBy2JgVQ+3hUrD9wZMkUw/ywgkpK7oZgnB6kPpGrxJ41clkPPnsknuD6M2Q==",
6595
+ "cpu": [
6596
+ "arm"
6597
+ ],
6598
+ "dev": true,
6599
+ "optional": true,
6600
+ "os": [
6601
+ "linux"
6602
+ ]
6603
+ },
6604
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
6605
+ "version": "4.19.1",
6606
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.19.1.tgz",
6607
+ "integrity": "sha512-DZNLwIY4ftPSRVkJEaxYkq7u2zel7aah57HESuNkUnz+3bZHxwkCUkrfS2IWC1sxK6F2QNIR0Qr/YXw7nkF3Pw==",
6608
+ "cpu": [
6609
+ "arm"
6610
+ ],
6611
+ "dev": true,
6612
+ "optional": true,
6613
+ "os": [
6614
+ "linux"
6615
+ ]
6616
+ },
6617
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
6618
+ "version": "4.19.1",
6619
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.19.1.tgz",
6620
+ "integrity": "sha512-C7evongnjyxdngSDRRSQv5GvyfISizgtk9RM+z2biV5kY6S/NF/wta7K+DanmktC5DkuaJQgoKGf7KUDmA7RUw==",
6621
+ "cpu": [
6622
+ "arm64"
6623
+ ],
6624
+ "dev": true,
6625
+ "optional": true,
6626
+ "os": [
6627
+ "linux"
6628
+ ]
6629
+ },
6630
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
6631
+ "version": "4.19.1",
6632
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.19.1.tgz",
6633
+ "integrity": "sha512-89tFWqxfxLLHkAthAcrTs9etAoBFRduNfWdl2xUs/yLV+7XDrJ5yuXMHptNqf1Zw0UCA3cAutkAiAokYCkaPtw==",
6634
+ "cpu": [
6635
+ "arm64"
6636
+ ],
6637
+ "dev": true,
6638
+ "optional": true,
6639
+ "os": [
6640
+ "linux"
6641
+ ]
6642
+ },
6643
+ "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
6644
+ "version": "4.19.1",
6645
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.19.1.tgz",
6646
+ "integrity": "sha512-PromGeV50sq+YfaisG8W3fd+Cl6mnOOiNv2qKKqKCpiiEke2KiKVyDqG/Mb9GWKbYMHj5a01fq/qlUR28PFhCQ==",
6647
+ "cpu": [
6648
+ "ppc64"
6649
+ ],
6650
+ "dev": true,
6651
+ "optional": true,
6652
+ "os": [
6653
+ "linux"
6654
+ ]
6655
+ },
6656
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
6657
+ "version": "4.19.1",
6658
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.19.1.tgz",
6659
+ "integrity": "sha512-/1BmHYh+iz0cNCP0oHCuF8CSiNj0JOGf0jRlSo3L/FAyZyG2rGBuKpkZVH9YF+x58r1jgWxvm1aRg3DHrLDt6A==",
6660
+ "cpu": [
6661
+ "riscv64"
6662
+ ],
6663
+ "dev": true,
6664
+ "optional": true,
6665
+ "os": [
6666
+ "linux"
6667
+ ]
6668
+ },
6669
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
6670
+ "version": "4.19.1",
6671
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.19.1.tgz",
6672
+ "integrity": "sha512-0cYP5rGkQWRZKy9/HtsWVStLXzCF3cCBTRI+qRL8Z+wkYlqN7zrSYm6FuY5Kd5ysS5aH0q5lVgb/WbG4jqXN1Q==",
6673
+ "cpu": [
6674
+ "s390x"
6675
+ ],
6676
+ "dev": true,
6677
+ "optional": true,
6678
+ "os": [
6679
+ "linux"
6680
+ ]
6681
+ },
6682
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
6683
+ "version": "4.19.1",
6684
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.19.1.tgz",
6685
+ "integrity": "sha512-XUXeI9eM8rMP8aGvii/aOOiMvTs7xlCosq9xCjcqI9+5hBxtjDpD+7Abm1ZhVIFE1J2h2VIg0t2DX/gjespC2Q==",
6686
+ "cpu": [
6687
+ "x64"
6688
+ ],
6689
+ "dev": true,
6690
+ "optional": true,
6691
+ "os": [
6692
+ "linux"
6693
+ ]
6694
+ },
6695
+ "node_modules/@rollup/rollup-linux-x64-musl": {
6696
+ "version": "4.19.1",
6697
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.19.1.tgz",
6698
+ "integrity": "sha512-V7cBw/cKXMfEVhpSvVZhC+iGifD6U1zJ4tbibjjN+Xi3blSXaj/rJynAkCFFQfoG6VZrAiP7uGVzL440Q6Me2Q==",
6699
+ "cpu": [
6700
+ "x64"
6701
+ ],
6702
+ "dev": true,
6703
+ "optional": true,
6704
+ "os": [
6705
+ "linux"
6706
+ ]
6707
+ },
6708
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
6709
+ "version": "4.19.1",
6710
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.19.1.tgz",
6711
+ "integrity": "sha512-88brja2vldW/76jWATlBqHEoGjJLRnP0WOEKAUbMcXaAZnemNhlAHSyj4jIwMoP2T750LE9lblvD4e2jXleZsA==",
6712
+ "cpu": [
6713
+ "arm64"
6714
+ ],
6715
+ "dev": true,
6716
+ "optional": true,
6717
+ "os": [
6718
+ "win32"
6719
+ ]
6720
+ },
6721
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
6722
+ "version": "4.19.1",
6723
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.19.1.tgz",
6724
+ "integrity": "sha512-LdxxcqRVSXi6k6JUrTah1rHuaupoeuiv38du8Mt4r4IPer3kwlTo+RuvfE8KzZ/tL6BhaPlzJ3835i6CxrFIRQ==",
6725
+ "cpu": [
6726
+ "ia32"
6727
+ ],
6728
+ "dev": true,
6729
+ "optional": true,
6730
+ "os": [
6731
+ "win32"
6732
+ ]
6733
+ },
6734
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
6735
+ "version": "4.19.1",
6736
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.19.1.tgz",
6737
+ "integrity": "sha512-2bIrL28PcK3YCqD9anGxDxamxdiJAxA+l7fWIwM5o8UqNy1t3d1NdAweO2XhA0KTDJ5aH1FsuiT5+7VhtHliXg==",
6738
+ "cpu": [
6739
+ "x64"
6740
+ ],
6741
+ "dev": true,
6742
+ "optional": true,
6743
+ "os": [
6744
+ "win32"
6745
+ ]
6746
+ },
6747
  "node_modules/@rushstack/eslint-patch": {
6748
  "version": "1.10.4",
6749
  "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.4.tgz",
 
7646
  }
7647
  },
7648
  "node_modules/@types/node": {
7649
+ "version": "20.14.13",
7650
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.13.tgz",
7651
+ "integrity": "sha512-+bHoGiZb8UiQ0+WEtmph2IWQCjIqg8MDZMAV+ppRRhUZnquF5mQkP/9vpSwJClEiSM/C7fZZExPzfU0vJTyp8w==",
7652
  "dependencies": {
7653
  "undici-types": "~5.26.4"
7654
  }
 
8022
  },
8023
  "node_modules/@xenova/transformers": {
8024
  "version": "3.0.0-alpha.0",
8025
+ "resolved": "git+ssh://git@github.com/xenova/transformers.js.git#38a3bf6dab2265d9f0c2f613064535863194e6b9",
8026
  "dependencies": {
8027
  "@huggingface/jinja": "^0.2.2",
8028
  "onnxruntime-web": "^1.18.0",
 
10435
  }
10436
  },
10437
  "node_modules/detect-gpu": {
10438
+ "version": "5.0.41",
10439
+ "resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.41.tgz",
10440
+ "integrity": "sha512-0avjQwm8zyDPLmmp2PlaUxOWp/CNLbOU4t61x1IOTmBvC7UO+NMWDlEJcIjtbRBSnulC2ote81Xyillssam0CA==",
10441
  "dependencies": {
10442
  "webgl-constants": "^1.1.1"
10443
  }
 
12260
  }
12261
  },
12262
  "node_modules/fast-xml-parser": {
12263
+ "version": "4.4.1",
12264
+ "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz",
12265
+ "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==",
12266
  "funding": [
12267
  {
12268
  "type": "github",
 
20918
  }
20919
  },
20920
  "node_modules/zod-to-json-schema": {
20921
+ "version": "3.23.2",
20922
+ "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.23.2.tgz",
20923
+ "integrity": "sha512-uSt90Gzc/tUfyNqxnjlfBs8W6WSGpNBv0rVsNxP/BVSMHMKGdthPYff4xtCHYloJGM0CFxFsb3NbC0eqPhfImw==",
20924
  "peerDependencies": {
20925
  "zod": "^3.23.3"
20926
  }
package.json CHANGED
@@ -38,9 +38,9 @@
38
  "dependencies": {
39
  "@aitube/broadway": "0.1.2",
40
  "@aitube/clap": "0.1.2",
41
- "@aitube/clapper-services": "0.1.2-7",
42
  "@aitube/engine": "0.1.2",
43
- "@aitube/timeline": "0.1.2-1",
44
  "@fal-ai/serverless-client": "^0.13.0",
45
  "@ffmpeg/ffmpeg": "^0.12.10",
46
  "@ffmpeg/util": "^0.12.1",
@@ -165,23 +165,23 @@
165
  "vitest": "^2.0.2"
166
  },
167
  "optionalDependencies": {
168
- "@img/sharp-win32-ia32": "0.33.4",
169
- "@img/sharp-win32-x64": "0.33.4",
170
  "@img/sharp-darwin-arm64": "0.33.4",
171
  "@img/sharp-darwin-x64": "0.33.4",
 
 
 
 
 
 
 
 
 
172
  "@img/sharp-linux-arm": "0.33.4",
173
  "@img/sharp-linux-arm64": "0.33.4",
174
  "@img/sharp-linux-x64": "0.33.4",
175
- "@img/sharp-linuxmusl-x64": "0.33.4",
176
  "@img/sharp-linuxmusl-arm64": "0.33.4",
177
- "@img/sharp-libvips-win32-x64": "1.0.2",
178
- "@img/sharp-libvips-win32-ia32": "1.0.2",
179
- "@img/sharp-libvips-linux-arm": "1.0.2",
180
- "@img/sharp-libvips-linux-x64": "1.0.2",
181
- "@img/sharp-libvips-linux-arm64": "1.0.2",
182
- "@img/sharp-libvips-linuxmusl-x64": "1.0.2",
183
- "@img/sharp-libvips-linuxmusl-arm64": "1.0.2",
184
- "@img/sharp-libvips-darwin-x64": "1.0.2",
185
- "@img/sharp-libvips-darwin-arm64": "1.0.2"
186
  }
187
  }
 
38
  "dependencies": {
39
  "@aitube/broadway": "0.1.2",
40
  "@aitube/clap": "0.1.2",
41
+ "@aitube/clapper-services": "0.1.2-8",
42
  "@aitube/engine": "0.1.2",
43
+ "@aitube/timeline": "0.1.2-2",
44
  "@fal-ai/serverless-client": "^0.13.0",
45
  "@ffmpeg/ffmpeg": "^0.12.10",
46
  "@ffmpeg/util": "^0.12.1",
 
165
  "vitest": "^2.0.2"
166
  },
167
  "optionalDependencies": {
 
 
168
  "@img/sharp-darwin-arm64": "0.33.4",
169
  "@img/sharp-darwin-x64": "0.33.4",
170
+ "@img/sharp-libvips-darwin-arm64": "1.0.2",
171
+ "@img/sharp-libvips-darwin-x64": "1.0.2",
172
+ "@img/sharp-libvips-linux-arm": "1.0.2",
173
+ "@img/sharp-libvips-linux-arm64": "1.0.2",
174
+ "@img/sharp-libvips-linux-x64": "1.0.2",
175
+ "@img/sharp-libvips-linuxmusl-arm64": "1.0.2",
176
+ "@img/sharp-libvips-linuxmusl-x64": "1.0.2",
177
+ "@img/sharp-libvips-win32-ia32": "1.0.2",
178
+ "@img/sharp-libvips-win32-x64": "1.0.2",
179
  "@img/sharp-linux-arm": "0.33.4",
180
  "@img/sharp-linux-arm64": "0.33.4",
181
  "@img/sharp-linux-x64": "0.33.4",
 
182
  "@img/sharp-linuxmusl-arm64": "0.33.4",
183
+ "@img/sharp-linuxmusl-x64": "0.33.4",
184
+ "@img/sharp-win32-ia32": "0.33.4",
185
+ "@img/sharp-win32-x64": "0.33.4"
 
 
 
 
 
 
186
  }
187
  }
{src/app β†’ public/images/logos}/logo-desaturated.png RENAMED
File without changes
public/images/logos/logo-discord-2.png ADDED

Git LFS Details

  • SHA256: 914019608bd1f1107e9d7d6477f26b98b07c55844dd7f53bb54fe8062c708bfa
  • Pointer size: 131 Bytes
  • Size of remote file: 568 kB
public/images/logos/logo-discord-3.png ADDED

Git LFS Details

  • SHA256: f26ab09e96f7b92408b73a1d212311f674d48f96a7a29b96a6cb27f8884c9212
  • Pointer size: 131 Bytes
  • Size of remote file: 309 kB
{src/app β†’ public/images/logos}/logo-no-bg.png RENAMED
File without changes
{src/app β†’ public/images/logos}/logo-v2.png RENAMED
File without changes
{src/app β†’ public/images/logos}/logo-v2.xcf RENAMED
File without changes
src/app/main.tsx CHANGED
@@ -118,10 +118,12 @@ function MainContent() {
118
  <FruityWindow
119
  id="ScriptEditor"
120
  title="Script editor"
121
- defaultWidth="450px"
122
- minWidth="200px"
123
- defaultHeight="350px"
124
- minHeight="100px"
 
 
125
  canBeClosed={false}
126
  >
127
  <ScriptEditor />
@@ -130,10 +132,12 @@ function MainContent() {
130
  <FruityWindow
131
  id="SegmentEditor"
132
  title="segment editor"
133
- defaultWidth="450px"
134
- minWidth="200px"
135
- defaultHeight="350px"
136
- minHeight="100px"
 
 
137
  canBeClosed={false}
138
  >
139
  <SegmentEditor />
@@ -142,10 +146,12 @@ function MainContent() {
142
  <FruityWindow
143
  id="EntityEditor"
144
  title="Entity editor"
145
- defaultWidth="450px"
146
- minWidth="200px"
147
- defaultHeight="350px"
148
- minHeight="100px"
 
 
149
  canBeClosed={false}
150
  >
151
  <EntityEditor />
@@ -155,10 +161,12 @@ function MainContent() {
155
  <FruityWindow
156
  id="WorkflowEditor"
157
  title="Workflow editor"
158
- defaultWidth="450px"
159
- minWidth="200px"
160
- defaultHeight="350px"
161
- minHeight="100px"
 
 
162
  canBeClosed={false}
163
  >
164
  <WorkflowEditor />
@@ -168,10 +176,12 @@ function MainContent() {
168
  <FruityWindow
169
  id="Monitor"
170
  title="Monitor"
171
- defaultWidth="450px"
172
- minWidth="200px"
173
- defaultHeight="350px"
174
- minHeight="100px"
 
 
175
  canBeClosed={false}
176
  >
177
  <Monitor />
@@ -180,10 +190,12 @@ function MainContent() {
180
  <FruityWindow
181
  id="Timeline"
182
  title="Timeline"
183
- defaultWidth="800px"
184
- minWidth="200px"
185
- defaultHeight="350px"
186
- minHeight="100px"
 
 
187
  canBeClosed={false}
188
  >
189
  <Timeline />
 
118
  <FruityWindow
119
  id="ScriptEditor"
120
  title="Script editor"
121
+ defaultWidth={375}
122
+ minWidth={200}
123
+ defaultHeight={370}
124
+ minHeight={100}
125
+ defaultX={18}
126
+ defaultY={7}
127
  canBeClosed={false}
128
  >
129
  <ScriptEditor />
 
132
  <FruityWindow
133
  id="SegmentEditor"
134
  title="segment editor"
135
+ defaultWidth={342}
136
+ minWidth={200}
137
+ defaultHeight={395}
138
+ minHeight={100}
139
+ defaultX={21}
140
+ defaultY={424}
141
  canBeClosed={false}
142
  >
143
  <SegmentEditor />
 
146
  <FruityWindow
147
  id="EntityEditor"
148
  title="Entity editor"
149
+ defaultWidth={544}
150
+ minWidth={200}
151
+ defaultHeight={318}
152
+ minHeight={100}
153
+ defaultX={347}
154
+ defaultY={193}
155
  canBeClosed={false}
156
  >
157
  <EntityEditor />
 
161
  <FruityWindow
162
  id="WorkflowEditor"
163
  title="Workflow editor"
164
+ defaultWidth={459}
165
+ minWidth={200}
166
+ defaultHeight={351}
167
+ minHeight={100}
168
+ defaultX={536}
169
+ defaultY={3}
170
  canBeClosed={false}
171
  >
172
  <WorkflowEditor />
 
176
  <FruityWindow
177
  id="Monitor"
178
  title="Monitor"
179
+ defaultWidth={333}
180
+ minWidth={200}
181
+ defaultHeight={298}
182
+ minHeight={100}
183
+ defaultX={1027}
184
+ defaultY={21}
185
  canBeClosed={false}
186
  >
187
  <Monitor />
 
190
  <FruityWindow
191
  id="Timeline"
192
  title="Timeline"
193
+ defaultWidth={936}
194
+ minWidth={200}
195
+ defaultHeight={282}
196
+ minHeight={100}
197
+ defaultX={375}
198
+ defaultY={527}
199
  canBeClosed={false}
200
  >
201
  <Timeline />
src/components/editors/EntityEditor/EntityTree/index.tsx CHANGED
@@ -1,11 +1,15 @@
1
  'use client'
2
 
 
 
 
3
  import { cn } from '@/lib/utils'
4
  import { isClapEntity } from '@/components/tree-browsers/utils/isSomething'
5
  import { TreeNodeItem, LibraryNodeType } from '@/components/tree-browsers/types'
6
  import { Tree } from '@/components/core/tree'
7
 
8
  import { useEntityTree } from './useEntityTree'
 
9
 
10
  export function EntityTree({
11
  className = '',
@@ -15,7 +19,15 @@ export function EntityTree({
15
  const libraryTreeRoot = useEntityTree((s) => s.libraryTreeRoot)
16
  const selectTreeNode = useEntityTree((s) => s.selectTreeNode)
17
  const selectedTreeNodeId = useEntityTree((s) => s.selectedTreeNodeId)
 
 
 
 
18
 
 
 
 
 
19
  /**
20
  * handle click on tree node
21
  * yes, this is where the magic happens!
 
1
  'use client'
2
 
3
+ import { useEffect } from 'react'
4
+ import { useTimeline } from '@aitube/timeline'
5
+
6
  import { cn } from '@/lib/utils'
7
  import { isClapEntity } from '@/components/tree-browsers/utils/isSomething'
8
  import { TreeNodeItem, LibraryNodeType } from '@/components/tree-browsers/types'
9
  import { Tree } from '@/components/core/tree'
10
 
11
  import { useEntityTree } from './useEntityTree'
12
+ import { ClapEntity } from '@aitube/clap'
13
 
14
  export function EntityTree({
15
  className = '',
 
19
  const libraryTreeRoot = useEntityTree((s) => s.libraryTreeRoot)
20
  const selectTreeNode = useEntityTree((s) => s.selectTreeNode)
21
  const selectedTreeNodeId = useEntityTree((s) => s.selectedTreeNodeId)
22
+ const setProjectEntities = useEntityTree((s) => s.setProjectEntities)
23
+
24
+ const entitiesChanged: number = useTimeline((s) => s.entitiesChanged)
25
+ const entities: ClapEntity[] = useTimeline((s) => s.entities)
26
 
27
+ useEffect(() => {
28
+ console.log('loading entities:', entities)
29
+ setProjectEntities(entities)
30
+ }, [entities, entitiesChanged, entities.length])
31
  /**
32
  * handle click on tree node
33
  * yes, this is where the magic happens!
src/components/editors/EntityEditor/EntityTree/useEntityTree.ts CHANGED
@@ -1,7 +1,7 @@
1
  'use client'
2
 
3
  import { create } from 'zustand'
4
- import { ClapEntity, UUID } from '@aitube/clap'
5
  import {
6
  LibraryTreeNode,
7
  TreeNodeItem,
@@ -11,6 +11,7 @@ import { icons } from '@/components/icons'
11
  import { getAppropriateIcon } from '@/components/icons/getAppropriateIcon'
12
  import {
13
  collectionClassName,
 
14
  libraryClassName,
15
  } from '@/components/tree-browsers/style/treeNodeStyles'
16
 
@@ -33,7 +34,7 @@ export const useEntityTree = create<{
33
  * @param collections
34
  * @returns
35
  */
36
- // setProjectLibrary: (collections: ProjectEntityCollection[]) => void
37
 
38
  /**
39
  * Load entity collections (characters, locations..) from the Clapper community into the tree
@@ -109,11 +110,10 @@ export const useEntityTree = create<{
109
  })
110
  },
111
 
112
- /*
113
  setProjectEntities: async (entities: ClapEntity[]) => {
114
  const characters: LibraryTreeNode = {
115
  id: UUID(),
116
- nodeType: 'LIB_NODE_GENERIC_COLLECTION',
117
  data: undefined,
118
  label: 'Characters',
119
  icon: icons.characters,
@@ -124,7 +124,7 @@ export const useEntityTree = create<{
124
 
125
  const locations: LibraryTreeNode = {
126
  id: UUID(),
127
- nodeType: 'LIB_NODE_GENERIC_COLLECTION',
128
  data: undefined,
129
  label: 'Locations',
130
  icon: icons.location,
@@ -135,7 +135,7 @@ export const useEntityTree = create<{
135
 
136
  const misc: LibraryTreeNode = {
137
  id: UUID(),
138
- nodeType: 'LIB_NODE_GENERIC_COLLECTION',
139
  data: undefined,
140
  label: 'Misc',
141
  icon: icons.misc,
@@ -146,7 +146,7 @@ export const useEntityTree = create<{
146
 
147
  entities.forEach((entity) => {
148
  const node: LibraryTreeNode = {
149
- nodeType: TreeNodeEntityItem,
150
  id: entity.id,
151
  data: entity,
152
  label: entity.label,
@@ -165,6 +165,7 @@ export const useEntityTree = create<{
165
  })
166
  },
167
 
 
168
  setCommunityCollections: (collections: CommunityEntityCollection[]) => {
169
  // TODO: implement this
170
 
 
1
  'use client'
2
 
3
  import { create } from 'zustand'
4
+ import { ClapEntity, ClapSegmentCategory, UUID } from '@aitube/clap'
5
  import {
6
  LibraryTreeNode,
7
  TreeNodeItem,
 
11
  import { getAppropriateIcon } from '@/components/icons/getAppropriateIcon'
12
  import {
13
  collectionClassName,
14
+ itemClassName,
15
  libraryClassName,
16
  } from '@/components/tree-browsers/style/treeNodeStyles'
17
 
 
34
  * @param collections
35
  * @returns
36
  */
37
+ setProjectEntities: (entities: ClapEntity[]) => Promise<void>
38
 
39
  /**
40
  * Load entity collections (characters, locations..) from the Clapper community into the tree
 
110
  })
111
  },
112
 
 
113
  setProjectEntities: async (entities: ClapEntity[]) => {
114
  const characters: LibraryTreeNode = {
115
  id: UUID(),
116
+ nodeType: 'ENTITY_TREE_NODE_LIST_ENTITIES',
117
  data: undefined,
118
  label: 'Characters',
119
  icon: icons.characters,
 
124
 
125
  const locations: LibraryTreeNode = {
126
  id: UUID(),
127
+ nodeType: 'ENTITY_TREE_NODE_LIST_ENTITIES',
128
  data: undefined,
129
  label: 'Locations',
130
  icon: icons.location,
 
135
 
136
  const misc: LibraryTreeNode = {
137
  id: UUID(),
138
+ nodeType: 'ENTITY_TREE_NODE_LIST_ENTITIES',
139
  data: undefined,
140
  label: 'Misc',
141
  icon: icons.misc,
 
146
 
147
  entities.forEach((entity) => {
148
  const node: LibraryTreeNode = {
149
+ nodeType: 'ENTITY_TREE_NODE_ITEM_ENTITY',
150
  id: entity.id,
151
  data: entity,
152
  label: entity.label,
 
165
  })
166
  },
167
 
168
+ /*
169
  setCommunityCollections: (collections: CommunityEntityCollection[]) => {
170
  // TODO: implement this
171
 
src/components/forms/FormSection.tsx CHANGED
@@ -19,8 +19,10 @@ export function FormSection({
19
  className
20
  )}
21
  >
22
- <h2 className="pb-2 text-xl font-normal text-white/60">{label}</h2>
23
- <div className={cn('flex w-full', 'flex-col space-y-4')}>{children}</div>
 
 
24
  </div>
25
  )
26
  }
 
19
  className
20
  )}
21
  >
22
+ <h2 className="pb-2 text-lg font-normal text-white/60">{label}</h2>
23
+ <div className={cn('flex w-full', 'flex-col space-y-4 text-sm')}>
24
+ {children}
25
+ </div>
26
  </div>
27
  )
28
  }
src/components/toolbars/top-menu/ToggleFullScreen/index.tsx ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useEffect } from 'react'
2
+ import { RiFullscreenLine, RiFullscreenExitLine } from 'react-icons/ri'
3
+
4
+ import { cn } from '@/lib/utils'
5
+ import {
6
+ Tooltip,
7
+ TooltipContent,
8
+ TooltipTrigger,
9
+ } from '@/components/ui/tooltip'
10
+ import { useFullscreenStatus } from '@/lib/hooks'
11
+
12
+ export function ToggleFullScreen({
13
+ className = '',
14
+ }: {
15
+ className?: string
16
+ } = {}) {
17
+ const [isFullscreen, setFullscreen, ref] = useFullscreenStatus()
18
+
19
+ // we want the whole body to become fullscreen
20
+ // TODO: use pointer lock, to prevent the mouse from going up
21
+ useEffect(() => {
22
+ if (typeof window !== 'undefined') {
23
+ ref.current = document.body
24
+ }
25
+ }, [ref])
26
+
27
+ return (
28
+ <Tooltip>
29
+ <TooltipTrigger className="">
30
+ <div
31
+ className={cn(
32
+ `grid h-4 w-5 scale-100 cursor-pointer grid-cols-4 grid-rows-4 overflow-hidden rounded border border-neutral-100 opacity-70 transition-all duration-200 ease-in-out hover:scale-110 hover:opacity-100`,
33
+ className
34
+ )}
35
+ onClick={() => {
36
+ setFullscreen(!isFullscreen)
37
+ }}
38
+ >
39
+ <div className="flex h-4 w-4 items-center justify-center">
40
+ <RiFullscreenLine
41
+ className={cn(
42
+ `absolute -mt-0.5 ml-0.5 h-3 w-3`,
43
+ `transition-all duration-200 ease-in-out`,
44
+ !isFullscreen ? 'opacity-100' : 'opacity-0'
45
+ )}
46
+ />
47
+ <RiFullscreenExitLine
48
+ className={cn(
49
+ `absolute -mt-0.5 ml-0.5 h-3 w-3`,
50
+ `transition-all duration-200 ease-in-out`,
51
+ isFullscreen ? 'opacity-100' : 'opacity-0'
52
+ )}
53
+ />
54
+ </div>
55
+ </div>
56
+ </TooltipTrigger>
57
+ <TooltipContent className={cn(`mr-4 mt-2 flex text-xs font-light`)}>
58
+ Toggle fullscreen
59
+ </TooltipContent>
60
+ </Tooltip>
61
+ )
62
+ }
src/components/toolbars/top-menu/ToggleView/index.tsx CHANGED
@@ -23,7 +23,7 @@ export function ToggleView({
23
  <TooltipTrigger className="">
24
  <div
25
  className={cn(
26
- `group grid h-4 w-5 scale-100 cursor-pointer grid-cols-4 grid-rows-4 overflow-hidden rounded border border-stone-100 opacity-70 transition-all duration-200 ease-in-out hover:scale-110 hover:opacity-100`
27
  )}
28
  onClick={() => {
29
  setVisible(!isVisible)
@@ -32,7 +32,9 @@ export function ToggleView({
32
  <div
33
  className={cn(
34
  `flex transition-all duration-200 ease-in-out group-hover:scale-110 group-hover:border-stone-100`,
35
- isVisible ? `border-stone-100 bg-stone-400` : `border-stone-400`,
 
 
36
  className
37
  )}
38
  ></div>
 
23
  <TooltipTrigger className="">
24
  <div
25
  className={cn(
26
+ `group grid h-4 w-5 scale-100 cursor-pointer grid-cols-4 grid-rows-4 overflow-hidden rounded border border-neutral-100 opacity-70 transition-all duration-200 ease-in-out hover:scale-110 hover:opacity-100`
27
  )}
28
  onClick={() => {
29
  setVisible(!isVisible)
 
32
  <div
33
  className={cn(
34
  `flex transition-all duration-200 ease-in-out group-hover:scale-110 group-hover:border-stone-100`,
35
+ isVisible
36
+ ? `border-neutral-100 bg-neutral-400`
37
+ : `border-neutral-400`,
38
  className
39
  )}
40
  ></div>
src/components/toolbars/top-menu/ToggleWindowLayout/index.tsx ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { VscMultipleWindows } from 'react-icons/vsc'
2
+ import { CiGrid32 } from 'react-icons/ci'
3
+
4
+ import { GiStrawberry } from 'react-icons/gi'
5
+ import { UIWindowLayout } from '@aitube/clapper-services'
6
+
7
+ import { cn } from '@/lib/utils'
8
+ import {
9
+ Tooltip,
10
+ TooltipContent,
11
+ TooltipTrigger,
12
+ } from '@/components/ui/tooltip'
13
+ import { useUI } from '@/services'
14
+ import { BiSolidWindowAlt } from 'react-icons/bi'
15
+
16
+ export function ToggleWindowLayout({
17
+ className = '',
18
+ }: {
19
+ className?: string
20
+ } = {}) {
21
+ const windowLayout = useUI((s) => s.windowLayout)
22
+ const setWindowLayout = useUI((s) => s.setWindowLayout)
23
+
24
+ return (
25
+ <Tooltip>
26
+ <TooltipTrigger className="">
27
+ <div
28
+ className={cn(
29
+ `grid h-4 w-5 scale-100 cursor-pointer grid-cols-4 grid-rows-4 overflow-hidden rounded border border-neutral-100 opacity-70 transition-all duration-200 ease-in-out hover:scale-110 hover:opacity-100`,
30
+ className
31
+ )}
32
+ onClick={() => {
33
+ setWindowLayout(
34
+ windowLayout === UIWindowLayout.FLYING
35
+ ? UIWindowLayout.GRID
36
+ : UIWindowLayout.FLYING
37
+ )
38
+ }}
39
+ >
40
+ <div className="flex h-4 w-4 items-center justify-center">
41
+ <GiStrawberry
42
+ className={cn(
43
+ `absolute -mt-0.5 ml-0.5 h-3 w-3`,
44
+ `transition-all duration-200 ease-in-out`,
45
+ windowLayout === UIWindowLayout.FLYING
46
+ ? 'opacity-100'
47
+ : 'opacity-0'
48
+ )}
49
+ />
50
+ <BiSolidWindowAlt
51
+ className={cn(
52
+ `absolute -mt-0.5 ml-0.5 h-3 w-3`,
53
+ `transition-all duration-200 ease-in-out`,
54
+ windowLayout === UIWindowLayout.GRID
55
+ ? 'opacity-100'
56
+ : 'opacity-0'
57
+ )}
58
+ />
59
+ </div>
60
+ </div>
61
+ </TooltipTrigger>
62
+ <TooltipContent className={cn(`mr-4 mt-2 flex text-xs font-light`)}>
63
+ Toggle layout
64
+ </TooltipContent>
65
+ </Tooltip>
66
+ )
67
+ }
src/components/toolbars/top-menu/index.tsx CHANGED
@@ -24,6 +24,8 @@ import {
24
  import { ToggleView } from './ToggleView'
25
  import { UIWindowLayout } from '@aitube/clapper-services'
26
  import { Tasks } from '../bottom-bar/tasks'
 
 
27
 
28
  export function TopMenu() {
29
  const isBusyResolving = useResolver((s) => s.isBusyResolving)
@@ -38,6 +40,7 @@ export function TopMenu() {
38
  const setShowAssistant = useUI((s) => s.setShowAssistant)
39
  const setIsTopMenuOpen = useUI((s) => s.setIsTopMenuOpen)
40
  const windowLayout = useUI((s) => s.windowLayout)
 
41
  const hasBetaAccess = useUI((s) => s.hasBetaAccess)
42
 
43
  return (
@@ -107,6 +110,10 @@ export function TopMenu() {
107
  </>
108
  )}
109
 
 
 
 
 
110
  <Tooltip>
111
  <TooltipTrigger className="">
112
  <div className="ml-2 scale-100 cursor-pointer text-stone-300/70 transition-all duration-200 ease-in-out hover:scale-110 hover:text-stone-100/90">
 
24
  import { ToggleView } from './ToggleView'
25
  import { UIWindowLayout } from '@aitube/clapper-services'
26
  import { Tasks } from '../bottom-bar/tasks'
27
+ import { ToggleWindowLayout } from './ToggleWindowLayout'
28
+ import { ToggleFullScreen } from './ToggleFullScreen'
29
 
30
  export function TopMenu() {
31
  const isBusyResolving = useResolver((s) => s.isBusyResolving)
 
40
  const setShowAssistant = useUI((s) => s.setShowAssistant)
41
  const setIsTopMenuOpen = useUI((s) => s.setIsTopMenuOpen)
42
  const windowLayout = useUI((s) => s.windowLayout)
43
+ const setWindowLayout = useUI((s) => s.setWindowLayout)
44
  const hasBetaAccess = useUI((s) => s.hasBetaAccess)
45
 
46
  return (
 
110
  </>
111
  )}
112
 
113
+ <ToggleFullScreen />
114
+
115
+ <ToggleWindowLayout />
116
+
117
  <Tooltip>
118
  <TooltipTrigger className="">
119
  <div className="ml-2 scale-100 cursor-pointer text-stone-300/70 transition-all duration-200 ease-in-out hover:scale-110 hover:text-stone-100/90">
src/components/toolbars/top-menu/view/index.tsx CHANGED
@@ -5,6 +5,7 @@ import { useEffect } from 'react'
5
  import {
6
  MenubarCheckboxItem,
7
  MenubarContent,
 
8
  MenubarMenu,
9
  MenubarSeparator,
10
  MenubarTrigger,
@@ -13,6 +14,10 @@ import { useFullscreenStatus } from '@/lib/hooks'
13
  import { useUI } from '@/services/ui'
14
  import { ThemeList } from '../lists/ThemeList'
15
  import { UIWindowLayout } from '@aitube/clapper-services'
 
 
 
 
16
 
17
  export function TopMenuView() {
18
  const [isFullscreen, setFullscreen, ref] = useFullscreenStatus()
@@ -49,14 +54,8 @@ export function TopMenuView() {
49
  <MenubarMenu>
50
  <MenubarTrigger>View</MenubarTrigger>
51
  <MenubarContent>
52
- <MenubarCheckboxItem
53
- checked={isFullscreen}
54
  onClick={(e) => {
55
- // currently isFullscreen is a bit buggy and might not reflect the correct value
56
- // setFullscreen(!isFullscreen)
57
-
58
- // so to be sure we use setFullscreen in "toggle" mode
59
- // (ie. we don't pass a boolean, so it will act as a current value switch)
60
  setFullscreen()
61
 
62
  e.stopPropagation()
@@ -64,11 +63,32 @@ export function TopMenuView() {
64
  return false
65
  }}
66
  >
67
- Toggle fullscreen
68
- </MenubarCheckboxItem>
69
-
70
- <MenubarCheckboxItem
71
- checked={windowLayout === UIWindowLayout.FLYING}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  onClick={(e) => {
73
  setWindowLayout(
74
  windowLayout === UIWindowLayout.FLYING
@@ -81,8 +101,34 @@ export function TopMenuView() {
81
  return false
82
  }}
83
  >
84
- πŸ₯­ Fruity mode (experimental)
85
- </MenubarCheckboxItem>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
 
87
  {/*
88
  <MenubarSeparator />
 
5
  import {
6
  MenubarCheckboxItem,
7
  MenubarContent,
8
+ MenubarItem,
9
  MenubarMenu,
10
  MenubarSeparator,
11
  MenubarTrigger,
 
14
  import { useUI } from '@/services/ui'
15
  import { ThemeList } from '../lists/ThemeList'
16
  import { UIWindowLayout } from '@aitube/clapper-services'
17
+ import { cn } from '@/lib/utils'
18
+ import { GiStrawberry } from 'react-icons/gi'
19
+ import { BiSolidWindowAlt } from 'react-icons/bi'
20
+ import { RiFullscreenExitLine, RiFullscreenLine } from 'react-icons/ri'
21
 
22
  export function TopMenuView() {
23
  const [isFullscreen, setFullscreen, ref] = useFullscreenStatus()
 
54
  <MenubarMenu>
55
  <MenubarTrigger>View</MenubarTrigger>
56
  <MenubarContent>
57
+ <MenubarItem
 
58
  onClick={(e) => {
 
 
 
 
 
59
  setFullscreen()
60
 
61
  e.stopPropagation()
 
63
  return false
64
  }}
65
  >
66
+ <div
67
+ className={cn(
68
+ `grid h-4 w-5 scale-100 cursor-pointer grid-cols-4 grid-rows-4 overflow-hidden rounded border border-neutral-100 opacity-70 transition-all duration-200 ease-in-out hover:scale-110 hover:opacity-100`
69
+ )}
70
+ >
71
+ <div className="flex h-4 w-4 items-center justify-center">
72
+ <RiFullscreenLine
73
+ className={cn(
74
+ `absolute -mt-0.5 ml-0.5 h-3 w-3`,
75
+ `transition-all duration-200 ease-in-out`,
76
+ !isFullscreen ? 'opacity-100' : 'opacity-0'
77
+ )}
78
+ />
79
+ <RiFullscreenExitLine
80
+ className={cn(
81
+ `absolute -mt-0.5 ml-0.5 h-3 w-3`,
82
+ `transition-all duration-200 ease-in-out`,
83
+ isFullscreen ? 'opacity-100' : 'opacity-0'
84
+ )}
85
+ />
86
+ </div>
87
+ </div>
88
+ <span className="ml-2">Toggle fullscreen</span>
89
+ </MenubarItem>
90
+
91
+ <MenubarItem
92
  onClick={(e) => {
93
  setWindowLayout(
94
  windowLayout === UIWindowLayout.FLYING
 
101
  return false
102
  }}
103
  >
104
+ <div
105
+ className={cn(
106
+ `grid h-4 w-5 scale-100 cursor-pointer grid-cols-4 grid-rows-4 overflow-hidden rounded border border-neutral-100 opacity-70 transition-all duration-200 ease-in-out hover:scale-110 hover:opacity-100`
107
+ )}
108
+ >
109
+ <div className="flex h-4 w-4 items-center justify-center">
110
+ <GiStrawberry
111
+ className={cn(
112
+ `absolute -mt-0.5 ml-0.5 h-3 w-3`,
113
+ `transition-all duration-200 ease-in-out`,
114
+ windowLayout === UIWindowLayout.FLYING
115
+ ? 'opacity-100'
116
+ : 'opacity-0'
117
+ )}
118
+ />
119
+ <BiSolidWindowAlt
120
+ className={cn(
121
+ `absolute -mt-0.5 ml-0.5 h-3 w-3`,
122
+ `transition-all duration-200 ease-in-out`,
123
+ windowLayout === UIWindowLayout.GRID
124
+ ? 'opacity-100'
125
+ : 'opacity-0'
126
+ )}
127
+ />
128
+ </div>
129
+ </div>
130
+ <span className="ml-2">Toggle layout</span>
131
+ </MenubarItem>
132
 
133
  {/*
134
  <MenubarSeparator />
src/components/windows/index.tsx CHANGED
@@ -1,4 +1,11 @@
1
- import React, { useState, useCallback, useRef, useEffect } from 'react'
 
 
 
 
 
 
 
2
  import { IoClose } from 'react-icons/io5'
3
  import { LuPanelTopClose, LuPanelTopOpen } from 'react-icons/lu'
4
 
@@ -7,6 +14,7 @@ import { useTheme } from '@/services'
7
  import { useWindows } from '@/services/windows/useWindows'
8
  import { useFullscreenStatus } from '@/lib/hooks'
9
  import { RiFullscreenFill } from 'react-icons/ri'
 
10
 
11
  // FruityDesktop component
12
  export const FruityDesktop: React.FC<{
@@ -29,7 +37,6 @@ export const FruityDesktop: React.FC<{
29
  )
30
  }
31
 
32
- // FruityWindow component
33
  export const FruityWindow: React.FC<{
34
  id: string
35
  title?: string | JSX.Element
@@ -44,372 +51,393 @@ export const FruityWindow: React.FC<{
44
  canBeFullScreen?: boolean
45
  toolbar?: (props: { isFocused: boolean }) => JSX.Element
46
  children?: React.ReactNode
47
- }> = ({
48
- id,
49
- title = 'Untitled',
50
- defaultWidth = 800,
51
- minWidth = 160,
52
- defaultHeight = 600,
53
- minHeight = 100,
54
- defaultX,
55
- defaultY,
56
- canBeReduced = true,
57
- canBeClosed = true,
58
- canBeFullScreen = true,
59
- toolbar,
60
- children,
61
- }) => {
62
- const theme = useTheme()
63
- const windowRef = useRef<HTMLDivElement>(null)
64
- const headerRef = useRef<HTMLDivElement>(null)
65
- const [isEditing, setIsEditing] = useState(false)
66
- const [isDragging, setIsDragging] = useState(false)
67
- const [isResizing, setIsResizing] = useState(false)
68
- const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 })
69
- const [resizeDirection, setResizeDirection] = useState('')
70
-
71
- const window = useWindows(useCallback((state) => state.windows[id], [id]))
72
- const addWindow = useWindows((state) => state.addWindow)
73
- const updateWindow = useWindows((state) => state.updateWindow)
74
- const focusWindow = useWindows((state) => state.focusWindow)
75
- const removeWindow = useWindows((state) => state.removeWindow)
76
-
77
- const [isFullscreen, setFullscreen, ref] = useFullscreenStatus()
78
-
79
- const parseSize = (size: number | string): number => {
80
- if (typeof size === 'number') return size
81
- if (typeof size === 'string') {
82
- const parsed = parseInt(size, 10)
83
- return isNaN(parsed) ? 0 : parsed
84
- }
85
- return 0
86
- }
87
-
88
- useEffect(() => {
89
- if (!window) {
90
- addWindow({
91
- id,
92
- title,
93
- isVisible: true,
94
- width: parseSize(defaultWidth),
95
- height: parseSize(defaultHeight),
96
- x: defaultX,
97
- y: defaultY,
98
- canBeReduced,
99
- canBeClosed,
100
- })
101
- }
102
- }, [
103
- addWindow,
104
- canBeClosed,
105
- canBeReduced,
106
- defaultHeight,
107
- defaultWidth,
108
  defaultX,
109
  defaultY,
110
- id,
111
- title,
112
- window,
113
- ])
 
 
 
 
 
 
 
 
 
 
114
 
115
- useEffect(() => {
116
- const handleMouseDown = (e: MouseEvent) => {
117
- if (windowRef.current && windowRef.current.contains(e.target as Node)) {
118
- focusWindow(id)
119
- }
120
- }
121
 
122
- document.addEventListener('mousedown', handleMouseDown)
123
- return () => {
124
- document.removeEventListener('mousedown', handleMouseDown)
125
- }
126
- }, [focusWindow, id])
127
 
128
- const handleDragStart = useCallback(
129
- (e: React.MouseEvent<HTMLDivElement>) => {
130
- if (window) {
131
- setIsDragging(true)
132
- setDragOffset({
133
- x: e.clientX - window.x,
134
- y: e.clientY - window.y,
135
- })
136
  }
137
- },
138
- [window]
139
- )
140
-
141
- const handleDrag = useCallback(
142
- (e: MouseEvent) => {
143
- if (isDragging && window) {
144
- const newY = Math.max(0, e.clientY - dragOffset.y) // Ensure y is at least 32px from the top
145
- updateWindow(id, {
146
- x: e.clientX - dragOffset.x,
147
- y: newY,
148
- })
149
- }
150
- },
151
- [dragOffset.x, dragOffset.y, id, isDragging, updateWindow, window]
152
- )
153
- const handleDragEnd = useCallback(() => {
154
- setIsDragging(false)
155
- }, [])
156
-
157
- useEffect(() => {
158
- if (isDragging) {
159
- document.addEventListener('mousemove', handleDrag)
160
- document.addEventListener('mouseup', handleDragEnd)
161
- } else {
162
- document.removeEventListener('mousemove', handleDrag)
163
- document.removeEventListener('mouseup', handleDragEnd)
164
  }
165
- return () => {
166
- document.removeEventListener('mousemove', handleDrag)
167
- document.removeEventListener('mouseup', handleDragEnd)
168
- }
169
- }, [isDragging, handleDrag, handleDragEnd])
170
 
171
- const handleResizeStart = useCallback(
172
- (e: React.MouseEvent<HTMLDivElement>, direction: string) => {
173
- e.preventDefault()
174
- e.stopPropagation()
175
- if (window) {
176
- setIsResizing(true)
177
- setResizeDirection(direction)
178
- setDragOffset({
179
- x: e.clientX,
180
- y: e.clientY,
 
 
181
  })
182
  }
183
- },
184
- [window]
185
- )
 
 
 
 
 
 
 
 
 
186
 
187
- const handleResize = useCallback(
188
- (e: MouseEvent) => {
189
- if (isResizing && window) {
190
- const dx = e.clientX - dragOffset.x
191
- const dy = e.clientY - dragOffset.y
192
- let newWidth = window.width
193
- let newHeight = window.height
194
- let newX = window.x
195
- let newY = window.y
196
 
197
- const parsedMinWidth = parseSize(minWidth)
198
- const parsedMinHeight = parseSize(minHeight)
 
 
 
199
 
200
- if (resizeDirection.includes('w')) {
201
- newWidth = Math.max(parsedMinWidth, window.width - dx)
202
- newX = window.x + window.width - newWidth
 
 
 
 
 
203
  }
204
- if (resizeDirection.includes('e')) {
205
- newWidth = Math.max(parsedMinWidth, window.width + dx)
206
- }
207
- if (resizeDirection.includes('n')) {
208
- newHeight = Math.max(parsedMinHeight, window.height - dy)
209
- newY = Math.max(0, window.y + window.height - newHeight)
210
- newHeight = window.y + window.height - newY // Adjust height based on the new Y position
 
 
 
 
 
211
  }
212
- if (resizeDirection.includes('s')) {
213
- newHeight = Math.max(parsedMinHeight, window.height + dy)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
214
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
 
216
- // Ensure the window doesn't go above the limit
217
- if (newY < 0) {
218
- newHeight = newHeight - (0 - newY)
219
- newY = 0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
221
 
222
- updateWindow(id, {
223
- width: newWidth,
224
- height: newHeight,
225
- x: newX,
226
- y: newY,
227
- })
228
- setDragOffset({ x: e.clientX, y: e.clientY })
 
 
 
 
 
229
  }
230
- },
231
- [
232
- dragOffset.x,
233
- dragOffset.y,
234
- id,
235
- isResizing,
236
- minHeight,
237
- minWidth,
238
- resizeDirection,
239
- updateWindow,
240
- window,
241
- ]
242
- )
243
 
244
- const handleResizeEnd = useCallback(() => {
245
- setIsResizing(false)
246
- setResizeDirection('')
247
- }, [])
 
248
 
249
- useEffect(() => {
250
- if (isResizing) {
251
- document.addEventListener('mousemove', handleResize)
252
- document.addEventListener('mouseup', handleResizeEnd)
253
- } else {
254
- document.removeEventListener('mousemove', handleResize)
255
- document.removeEventListener('mouseup', handleResizeEnd)
256
- }
257
- return () => {
258
- document.removeEventListener('mousemove', handleResize)
259
- document.removeEventListener('mouseup', handleResizeEnd)
260
- }
261
- }, [isResizing, handleResize, handleResizeEnd])
262
 
263
- const toggleReduce = useCallback(() => {
264
- if (window) {
265
- updateWindow(id, { isReduced: !window.isReduced })
266
- }
267
- }, [id, updateWindow, window])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
268
 
269
- const handleHeaderDoubleClick = useCallback(
270
- (e: React.MouseEvent<HTMLDivElement>) => {
271
- // Check if the double-click occurred on the header background
272
- if (e.target === headerRef.current) {
273
- toggleReduce()
274
- }
275
- },
276
- [toggleReduce]
277
- )
 
 
 
278
 
279
- if (!window) return null
280
 
281
- return (
282
- <div
283
- ref={windowRef}
284
- style={{
285
- width: isFullscreen ? '100vw' : `${window.width}px`,
286
- height: isFullscreen
287
- ? '100vh'
288
- : window.isReduced
289
- ? 'auto'
290
- : `${window.height}px`,
291
- transform: isFullscreen
292
- ? 'none'
293
- : `translate(${window.x}px, ${window.y}px)`,
294
- zIndex: window.zIndex,
295
- backgroundColor: theme.editorBgColor || 'rgb(38, 38, 38)',
296
- borderColor:
297
- theme.windowBorderColor ||
298
- theme.editorBorderColor ||
299
- 'rgb(64, 64, 64)',
300
- borderRadius: isFullscreen ? '0' : theme.windowBorderRadius || '8px',
301
- }}
302
- className={cn(
303
- `absolute overflow-hidden shadow-lg`,
304
- `border border-white/5`,
305
- window.isFocused ? 'shadow-xl' : '',
306
- isFullscreen ? 'fixed inset-0' : ''
307
- )}
308
- >
309
- {!isFullscreen && (
310
- <div
311
- ref={headerRef}
312
- className={cn(
313
- `flex h-8 cursor-move items-center justify-between border-b border-b-white/10 px-2 text-sm text-white/60`
314
- )}
315
- onMouseDown={handleDragStart}
316
- onDoubleClick={handleHeaderDoubleClick}
317
- style={{
318
- backgroundColor: theme.editorMenuBgColor || 'rgb(38, 38, 38)',
319
- }}
320
- >
321
- {isEditing ? (
322
- <input
323
- type="text"
324
- value={typeof window.title === 'string' ? window.title : ''}
325
- onChange={(e) => updateWindow(id, { title: e.target.value })}
326
- onBlur={() => setIsEditing(false)}
327
- onKeyDown={(e) => {
328
- if (e.key === 'Enter') setIsEditing(false)
329
- }}
330
- className="rounded-none bg-neutral-950/80 px-0 text-sm text-white/60"
331
- autoFocus
332
- />
333
- ) : (
334
- <div onDoubleClick={() => setIsEditing(true)}>
335
- {typeof window.title === 'string'
336
- ? window.title
337
- : (window.title as any)({ isFocused: window.isFocused })}
338
- </div>
339
- )}
340
- <div className="flex space-x-2">
341
- {toolbar && toolbar({ isFocused: window.isFocused })}
342
- {canBeFullScreen && (
343
- <button
344
- onClick={() => setFullscreen(true)}
345
- className="text-white/60 hover:text-white/80"
346
- >
347
- <RiFullscreenFill />
348
- </button>
349
- )}
350
- {canBeReduced && (
351
- <button
352
- onClick={toggleReduce}
353
- className="text-white/60 hover:text-white/80"
354
- >
355
- {window.isReduced ? <LuPanelTopOpen /> : <LuPanelTopClose />}
356
- </button>
357
  )}
358
- {canBeClosed && (
359
- <button
360
- onClick={() => removeWindow(id)}
361
- className="text-white/60 hover:text-white/80"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
362
  >
363
- <IoClose />
364
- </button>
 
 
365
  )}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
366
  </div>
367
- </div>
368
- )}
369
- {!window.isReduced && (
370
- <div
371
- ref={ref as any}
372
- className={isFullscreen ? 'h-full w-full' : 'h-[calc(100%-32px)]'}
373
- >
374
- {children}
375
- </div>
376
- )}
377
- {!isFullscreen && !window.isReduced && (
378
- <>
379
  <div
380
- className="absolute left-0 top-0 h-full w-2 cursor-ew-resize"
381
- onMouseDown={(e) => handleResizeStart(e, 'w')}
382
- />
383
- <div
384
- className="absolute right-0 top-0 h-full w-2 cursor-ew-resize"
385
- onMouseDown={(e) => handleResizeStart(e, 'e')}
386
- />
387
- <div
388
- className="absolute left-0 top-0 h-2 w-full cursor-ns-resize"
389
- onMouseDown={(e) => handleResizeStart(e, 'n')}
390
- />
391
- <div
392
- className="absolute bottom-0 left-0 h-2 w-full cursor-ns-resize"
393
- onMouseDown={(e) => handleResizeStart(e, 's')}
394
- />
395
- <div
396
- className="absolute left-0 top-0 h-2 w-2 cursor-nw-resize"
397
- onMouseDown={(e) => handleResizeStart(e, 'nw')}
398
- />
399
- <div
400
- className="absolute right-0 top-0 h-2 w-2 cursor-ne-resize"
401
- onMouseDown={(e) => handleResizeStart(e, 'ne')}
402
- />
403
- <div
404
- className="absolute bottom-0 left-0 h-2 w-2 cursor-sw-resize"
405
- onMouseDown={(e) => handleResizeStart(e, 'sw')}
406
- />
407
- <div
408
- className="absolute bottom-0 right-0 h-2 w-2 cursor-se-resize"
409
- onMouseDown={(e) => handleResizeStart(e, 'se')}
410
- />
411
- </>
412
- )}
413
- </div>
414
- )
415
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, {
2
+ useState,
3
+ useCallback,
4
+ useRef,
5
+ useEffect,
6
+ useMemo,
7
+ memo,
8
+ } from 'react'
9
  import { IoClose } from 'react-icons/io5'
10
  import { LuPanelTopClose, LuPanelTopOpen } from 'react-icons/lu'
11
 
 
14
  import { useWindows } from '@/services/windows/useWindows'
15
  import { useFullscreenStatus } from '@/lib/hooks'
16
  import { RiFullscreenFill } from 'react-icons/ri'
17
+ import { isValidNumber } from '@aitube/clap'
18
 
19
  // FruityDesktop component
20
  export const FruityDesktop: React.FC<{
 
37
  )
38
  }
39
 
 
40
  export const FruityWindow: React.FC<{
41
  id: string
42
  title?: string | JSX.Element
 
51
  canBeFullScreen?: boolean
52
  toolbar?: (props: { isFocused: boolean }) => JSX.Element
53
  children?: React.ReactNode
54
+ }> = memo(
55
+ ({
56
+ id,
57
+ title = 'Untitled',
58
+ defaultWidth = 800,
59
+ minWidth = 160,
60
+ defaultHeight = 600,
61
+ minHeight = 100,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  defaultX,
63
  defaultY,
64
+ canBeReduced = true,
65
+ canBeClosed = true,
66
+ canBeFullScreen = true,
67
+ toolbar,
68
+ children,
69
+ }) => {
70
+ const theme = useTheme()
71
+ const windowRef = useRef<HTMLDivElement>(null)
72
+ const headerRef = useRef<HTMLDivElement>(null)
73
+ const [isEditing, setIsEditing] = useState(false)
74
+ const [isDragging, setIsDragging] = useState(false)
75
+ const [isResizing, setIsResizing] = useState(false)
76
+ const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 })
77
+ const [resizeDirection, setResizeDirection] = useState('')
78
 
79
+ const window = useWindows(useCallback((state) => state.windows[id], [id]))
80
+ const addWindow = useWindows((state) => state.addWindow)
81
+ const updateWindow = useWindows((state) => state.updateWindow)
82
+ const focusWindow = useWindows((state) => state.focusWindow)
83
+ const removeWindow = useWindows((state) => state.removeWindow)
 
84
 
85
+ const [isFullscreen, setFullscreen, ref] = useFullscreenStatus()
 
 
 
 
86
 
87
+ const parseSize = (size: number | string): number => {
88
+ if (typeof size === 'number') return size
89
+ if (typeof size === 'string') {
90
+ const parsed = parseInt(size, 10)
91
+ return isNaN(parsed) ? 0 : parsed
 
 
 
92
  }
93
+ return 0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  }
 
 
 
 
 
95
 
96
+ useEffect(() => {
97
+ if (!window) {
98
+ addWindow({
99
+ id,
100
+ title,
101
+ isVisible: true,
102
+ width: parseSize(defaultWidth),
103
+ height: parseSize(defaultHeight),
104
+ x: defaultX,
105
+ y: defaultY,
106
+ canBeReduced,
107
+ canBeClosed,
108
  })
109
  }
110
+ }, [
111
+ addWindow,
112
+ canBeClosed,
113
+ canBeReduced,
114
+ defaultHeight,
115
+ defaultWidth,
116
+ defaultX,
117
+ defaultY,
118
+ id,
119
+ title,
120
+ window,
121
+ ])
122
 
123
+ useEffect(() => {
124
+ const handleMouseDown = (e: MouseEvent) => {
125
+ if (windowRef.current && windowRef.current.contains(e.target as Node)) {
126
+ focusWindow(id)
127
+ }
128
+ }
 
 
 
129
 
130
+ document.addEventListener('mousedown', handleMouseDown)
131
+ return () => {
132
+ document.removeEventListener('mousedown', handleMouseDown)
133
+ }
134
+ }, [focusWindow, id])
135
 
136
+ const handleDragStart = useCallback(
137
+ (e: React.MouseEvent<HTMLDivElement>) => {
138
+ if (isValidNumber(window?.x) && isValidNumber(window?.y)) {
139
+ setIsDragging(true)
140
+ setDragOffset({
141
+ x: e.clientX - window.x,
142
+ y: e.clientY - window.y,
143
+ })
144
  }
145
+ },
146
+ [window?.x, window?.y, setIsDragging, setDragOffset]
147
+ )
148
+
149
+ const handleDrag = useCallback(
150
+ (e: MouseEvent) => {
151
+ if (isDragging && window) {
152
+ const newY = Math.max(0, e.clientY - dragOffset.y) // Ensure y is at least 32px from the top
153
+ updateWindow(id, {
154
+ x: e.clientX - dragOffset.x,
155
+ y: newY,
156
+ })
157
  }
158
+ },
159
+ [dragOffset.x, dragOffset.y, id, isDragging, updateWindow, window]
160
+ )
161
+ const handleDragEnd = useCallback(() => {
162
+ setIsDragging(false)
163
+ }, [])
164
+
165
+ useEffect(() => {
166
+ if (isDragging) {
167
+ document.addEventListener('mousemove', handleDrag)
168
+ document.addEventListener('mouseup', handleDragEnd)
169
+ } else {
170
+ document.removeEventListener('mousemove', handleDrag)
171
+ document.removeEventListener('mouseup', handleDragEnd)
172
+ }
173
+ return () => {
174
+ document.removeEventListener('mousemove', handleDrag)
175
+ document.removeEventListener('mouseup', handleDragEnd)
176
+ }
177
+ }, [isDragging, handleDrag, handleDragEnd])
178
+
179
+ const handleResizeStart = useCallback(
180
+ (e: React.MouseEvent<HTMLDivElement>, direction: string) => {
181
+ e.preventDefault()
182
+ e.stopPropagation()
183
+ if (window) {
184
+ setIsResizing(true)
185
+ setResizeDirection(direction)
186
+ setDragOffset({
187
+ x: e.clientX,
188
+ y: e.clientY,
189
+ })
190
  }
191
+ },
192
+ [window, setIsResizing, setResizeDirection, setDragOffset]
193
+ )
194
+
195
+ const handleResize = useCallback(
196
+ (e: MouseEvent) => {
197
+ if (isResizing && window) {
198
+ const dx = e.clientX - dragOffset.x
199
+ const dy = e.clientY - dragOffset.y
200
+ let newWidth = window.width
201
+ let newHeight = window.height
202
+ let newX = window.x
203
+ let newY = window.y
204
+
205
+ const parsedMinWidth = parseSize(minWidth)
206
+ const parsedMinHeight = parseSize(minHeight)
207
 
208
+ if (resizeDirection.includes('w')) {
209
+ newWidth = Math.max(parsedMinWidth, window.width - dx)
210
+ newX = window.x + window.width - newWidth
211
+ }
212
+ if (resizeDirection.includes('e')) {
213
+ newWidth = Math.max(parsedMinWidth, window.width + dx)
214
+ }
215
+ if (resizeDirection.includes('n')) {
216
+ newHeight = Math.max(parsedMinHeight, window.height - dy)
217
+ newY = Math.max(0, window.y + window.height - newHeight)
218
+ newHeight = window.y + window.height - newY // Adjust height based on the new Y position
219
+ }
220
+ if (resizeDirection.includes('s')) {
221
+ newHeight = Math.max(parsedMinHeight, window.height + dy)
222
+ }
223
+
224
+ // Ensure the window doesn't go above the limit
225
+ if (newY < 0) {
226
+ newHeight = newHeight - (0 - newY)
227
+ newY = 0
228
+ }
229
+
230
+ updateWindow(id, {
231
+ width: newWidth,
232
+ height: newHeight,
233
+ x: newX,
234
+ y: newY,
235
+ })
236
+ setDragOffset({ x: e.clientX, y: e.clientY })
237
  }
238
+ },
239
+ [
240
+ dragOffset.x,
241
+ dragOffset.y,
242
+ id,
243
+ isResizing,
244
+ minHeight,
245
+ minWidth,
246
+ resizeDirection,
247
+ updateWindow,
248
+ window,
249
+ ]
250
+ )
251
 
252
+ const handleResizeEnd = useCallback(() => {
253
+ setIsResizing(false)
254
+ setResizeDirection('')
255
+ }, [])
256
+
257
+ useEffect(() => {
258
+ if (isResizing) {
259
+ document.addEventListener('mousemove', handleResize)
260
+ document.addEventListener('mouseup', handleResizeEnd)
261
+ } else {
262
+ document.removeEventListener('mousemove', handleResize)
263
+ document.removeEventListener('mouseup', handleResizeEnd)
264
  }
265
+ return () => {
266
+ document.removeEventListener('mousemove', handleResize)
267
+ document.removeEventListener('mouseup', handleResizeEnd)
268
+ }
269
+ }, [isResizing, handleResize, handleResizeEnd])
 
 
 
 
 
 
 
 
270
 
271
+ const toggleReduce = useCallback(() => {
272
+ if (window) {
273
+ updateWindow(id, { isReduced: !window.isReduced })
274
+ }
275
+ }, [id, updateWindow, window])
276
 
277
+ const handleHeaderDoubleClick = useCallback(
278
+ (e: React.MouseEvent<HTMLDivElement>) => {
279
+ // Check if the double-click occurred on the header background
280
+ if (e.target === headerRef.current) {
281
+ toggleReduce()
282
+ }
283
+ },
284
+ [toggleReduce]
285
+ )
 
 
 
 
286
 
287
+ const windowStyle = useMemo(
288
+ () =>
289
+ window
290
+ ? {
291
+ width: isFullscreen ? '100vw' : `${window?.width}px`,
292
+ height: isFullscreen
293
+ ? '100vh'
294
+ : window.isReduced
295
+ ? 'auto'
296
+ : `${window.height}px`,
297
+ transform: isFullscreen
298
+ ? 'none'
299
+ : `translate(${window.x}px, ${window.y}px)`,
300
+ zIndex: window.zIndex,
301
+ backgroundColor: theme.editorBgColor || 'rgb(38, 38, 38)',
302
+ borderColor:
303
+ theme.windowBorderColor ||
304
+ theme.editorBorderColor ||
305
+ 'rgb(64, 64, 64)',
306
+ borderRadius: isFullscreen
307
+ ? '0'
308
+ : theme.windowBorderRadius || '8px',
309
+ }
310
+ : {},
311
+ [isFullscreen, window, theme]
312
+ )
313
 
314
+ const windowClassName = useMemo(
315
+ () =>
316
+ window
317
+ ? cn(
318
+ `absolute overflow-hidden shadow-lg`,
319
+ `border border-white/5`,
320
+ window.isFocused ? 'shadow-xl' : '',
321
+ isFullscreen ? 'fixed inset-0' : ''
322
+ )
323
+ : 'display-none',
324
+ [window, window?.isFocused, isFullscreen]
325
+ )
326
 
327
+ if (!window) return null
328
 
329
+ return (
330
+ <div ref={windowRef} style={windowStyle} className={windowClassName}>
331
+ {!isFullscreen && (
332
+ <div
333
+ ref={headerRef}
334
+ className={cn(
335
+ `flex h-8 cursor-move items-center justify-between border-b border-b-white/10 px-2 text-sm text-white/60`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
336
  )}
337
+ onMouseDown={handleDragStart}
338
+ onDoubleClick={handleHeaderDoubleClick}
339
+ style={{
340
+ backgroundColor: theme.editorMenuBgColor || 'rgb(38, 38, 38)',
341
+ }}
342
+ >
343
+ {isEditing ? (
344
+ <input
345
+ type="text"
346
+ value={typeof window.title === 'string' ? window.title : ''}
347
+ onChange={(e) => updateWindow(id, { title: e.target.value })}
348
+ onBlur={() => setIsEditing(false)}
349
+ onKeyDown={(e) => {
350
+ if (e.key === 'Enter') setIsEditing(false)
351
+ }}
352
+ className="rounded-none bg-neutral-950/80 px-0 text-sm text-white/60"
353
+ autoFocus
354
+ />
355
+ ) : (
356
+ <div
357
+ // let's disable title editing, we don't need that for now
358
+ // onDoubleClick={() => setIsEditing(true)}
359
  >
360
+ {typeof window.title === 'string'
361
+ ? window.title
362
+ : (window.title as any)({ isFocused: window.isFocused })}
363
+ </div>
364
  )}
365
+ <div className="flex space-x-2">
366
+ {toolbar && toolbar({ isFocused: window.isFocused })}
367
+ {canBeFullScreen && (
368
+ <button
369
+ onClick={() => setFullscreen(true)}
370
+ className="text-white/60 hover:text-white/80"
371
+ >
372
+ <RiFullscreenFill />
373
+ </button>
374
+ )}
375
+ {canBeReduced && (
376
+ <button
377
+ onClick={toggleReduce}
378
+ className="text-white/60 hover:text-white/80"
379
+ >
380
+ {window.isReduced ? <LuPanelTopOpen /> : <LuPanelTopClose />}
381
+ </button>
382
+ )}
383
+ {canBeClosed && (
384
+ <button
385
+ onClick={() => removeWindow(id)}
386
+ className="text-white/60 hover:text-white/80"
387
+ >
388
+ <IoClose />
389
+ </button>
390
+ )}
391
+ </div>
392
  </div>
393
+ )}
394
+ {!window.isReduced && (
 
 
 
 
 
 
 
 
 
 
395
  <div
396
+ ref={ref as any}
397
+ className={isFullscreen ? 'h-full w-full' : 'h-[calc(100%-32px)]'}
398
+ >
399
+ {children}
400
+ </div>
401
+ )}
402
+ {!isFullscreen && !window.isReduced && (
403
+ <>
404
+ <div
405
+ className="absolute left-0 top-0 h-full w-2 cursor-ew-resize"
406
+ onMouseDown={(e) => handleResizeStart(e, 'w')}
407
+ />
408
+ <div
409
+ className="absolute right-0 top-0 h-full w-2 cursor-ew-resize"
410
+ onMouseDown={(e) => handleResizeStart(e, 'e')}
411
+ />
412
+ <div
413
+ className="absolute left-0 top-0 h-2 w-full cursor-ns-resize"
414
+ onMouseDown={(e) => handleResizeStart(e, 'n')}
415
+ />
416
+ <div
417
+ className="absolute bottom-0 left-0 h-2 w-full cursor-ns-resize"
418
+ onMouseDown={(e) => handleResizeStart(e, 's')}
419
+ />
420
+ <div
421
+ className="absolute left-0 top-0 h-2 w-2 cursor-nw-resize"
422
+ onMouseDown={(e) => handleResizeStart(e, 'nw')}
423
+ />
424
+ <div
425
+ className="absolute right-0 top-0 h-2 w-2 cursor-ne-resize"
426
+ onMouseDown={(e) => handleResizeStart(e, 'ne')}
427
+ />
428
+ <div
429
+ className="absolute bottom-0 left-0 h-2 w-2 cursor-sw-resize"
430
+ onMouseDown={(e) => handleResizeStart(e, 'sw')}
431
+ />
432
+ <div
433
+ className="absolute bottom-0 right-0 h-2 w-2 cursor-se-resize"
434
+ onMouseDown={(e) => handleResizeStart(e, 'se')}
435
+ />
436
+ </>
437
+ )}
438
+ </div>
439
+ )
440
+ }
441
+ )
442
+
443
+ FruityWindow.displayName = 'FruityWindow'
src/lib/core/constants.ts CHANGED
@@ -3,7 +3,7 @@
3
  export const HARD_LIMIT_NB_MAX_ASSETS_TO_GENERATE_IN_PARALLEL = 32
4
 
5
  export const APP_NAME = 'Clapper.app'
6
- export const APP_REVISION = '20240729+1107'
7
 
8
  export const APP_DOMAIN = 'Clapper.app'
9
  export const APP_LINK = 'https://clapper.app'
 
3
  export const HARD_LIMIT_NB_MAX_ASSETS_TO_GENERATE_IN_PARALLEL = 32
4
 
5
  export const APP_NAME = 'Clapper.app'
6
+ export const APP_REVISION = '20240729+1441'
7
 
8
  export const APP_DOMAIN = 'Clapper.app'
9
  export const APP_LINK = 'https://clapper.app'
src/lib/hooks/useFullscreenStatus.ts CHANGED
@@ -2,17 +2,17 @@
2
 
3
  import React, {
4
  useState,
5
- useLayoutEffect,
6
  useRef,
7
  MutableRefObject,
8
  useCallback,
9
  } from 'react'
10
 
11
  interface FullscreenElement {
12
- fullscreenElement?: Element
13
- mozFullScreenElement?: Element
14
- msFullscreenElement?: Element
15
- webkitFullscreenElement?: Element
16
  }
17
 
18
  declare global {
@@ -21,66 +21,68 @@ declare global {
21
 
22
  export function useFullscreenStatus(): [
23
  boolean,
24
- (requestedValue?: boolean) => void,
25
  MutableRefObject<Element | null>,
26
  ] {
27
  const elRef = useRef<Element | null>(null)
28
  const [isFullscreen, setIsFullscreen] = useState(false)
29
 
30
- const getFullscreenStatus = useCallback(() => {
31
- return typeof document !== 'undefined'
32
- ? Boolean(
33
- (document as FullscreenElement)[getBrowserFullscreenElementProp()]
34
- )
35
- : false
36
  }, [])
37
 
38
  const updateFullscreenStatus = useCallback(() => {
39
- setIsFullscreen(getFullscreenStatus())
40
- }, [getFullscreenStatus])
 
 
 
 
 
41
 
42
  const setFullscreen = useCallback(
43
- (maybeValue?: boolean) => {
44
  if (!elRef.current) return
45
 
46
- const isFullScreen = getFullscreenStatus()
47
- const shouldBeFullScreen =
48
- typeof maybeValue === 'boolean' ? maybeValue : !isFullScreen
49
 
50
- if (isFullScreen === shouldBeFullScreen) {
51
- return
52
- }
53
 
54
- const operation = shouldBeFullScreen
55
- ? elRef.current.requestFullscreen()
56
- : typeof document !== 'undefined'
57
- ? document.exitFullscreen()
58
- : Promise.resolve(true)
 
 
 
 
59
 
60
- operation.then(updateFullscreenStatus).catch(updateFullscreenStatus)
 
61
  },
62
- [getFullscreenStatus, updateFullscreenStatus]
63
  )
64
 
65
- useLayoutEffect(() => {
66
- updateFullscreenStatus()
67
-
68
  const fullscreenChangeHandler = () => {
69
- updateFullscreenStatus()
70
  }
71
 
72
  if (typeof document !== 'undefined') {
73
  document.addEventListener('fullscreenchange', fullscreenChangeHandler)
74
 
75
- // Polling mechanism
76
- const intervalId = setInterval(updateFullscreenStatus, 100)
77
 
78
  return () => {
79
  document.removeEventListener(
80
  'fullscreenchange',
81
  fullscreenChangeHandler
82
  )
83
- clearInterval(intervalId)
84
  }
85
  }
86
  }, [updateFullscreenStatus])
@@ -89,15 +91,20 @@ export function useFullscreenStatus(): [
89
  }
90
 
91
  function getBrowserFullscreenElementProp(): keyof FullscreenElement {
92
- if (typeof document.fullscreenElement !== 'undefined') {
93
- return 'fullscreenElement'
94
- } else if (typeof document.mozFullScreenElement !== 'undefined') {
95
- return 'mozFullScreenElement'
96
- } else if (typeof document.msFullscreenElement !== 'undefined') {
97
- return 'msFullscreenElement'
98
- } else if (typeof document.webkitFullscreenElement !== 'undefined') {
99
- return 'webkitFullscreenElement'
100
- } else {
101
- throw new Error('fullscreenElement is not supported by this browser')
 
 
 
102
  }
 
 
103
  }
 
2
 
3
  import React, {
4
  useState,
5
+ useEffect,
6
  useRef,
7
  MutableRefObject,
8
  useCallback,
9
  } from 'react'
10
 
11
  interface FullscreenElement {
12
+ fullscreenElement?: Element | null
13
+ mozFullScreenElement?: Element | null
14
+ msFullscreenElement?: Element | null
15
+ webkitFullscreenElement?: Element | null
16
  }
17
 
18
  declare global {
 
21
 
22
  export function useFullscreenStatus(): [
23
  boolean,
24
+ (requestedValue?: boolean) => Promise<void>,
25
  MutableRefObject<Element | null>,
26
  ] {
27
  const elRef = useRef<Element | null>(null)
28
  const [isFullscreen, setIsFullscreen] = useState(false)
29
 
30
+ const getFullscreenElement = useCallback((): Element | null => {
31
+ if (typeof document === 'undefined') return null
32
+ const fullscreenProp = getBrowserFullscreenElementProp()
33
+ return document[fullscreenProp] || null
 
 
34
  }, [])
35
 
36
  const updateFullscreenStatus = useCallback(() => {
37
+ const fullscreenElement = getFullscreenElement()
38
+ setIsFullscreen(
39
+ !!elRef.current &&
40
+ !!fullscreenElement &&
41
+ fullscreenElement === elRef.current
42
+ )
43
+ }, [getFullscreenElement])
44
 
45
  const setFullscreen = useCallback(
46
+ async (requestedValue?: boolean) => {
47
  if (!elRef.current) return
48
 
49
+ const isCurrentlyFullscreen = getFullscreenElement() === elRef.current
50
+ const shouldBeFullScreen = requestedValue ?? !isCurrentlyFullscreen
 
51
 
52
+ if (isCurrentlyFullscreen === shouldBeFullScreen) return
 
 
53
 
54
+ try {
55
+ if (shouldBeFullScreen) {
56
+ await elRef.current.requestFullscreen()
57
+ } else if (document.fullscreenElement) {
58
+ await document.exitFullscreen()
59
+ }
60
+ } catch (error) {
61
+ console.error('Fullscreen error:', error)
62
+ }
63
 
64
+ // Wait for the next frame before updating status
65
+ requestAnimationFrame(updateFullscreenStatus)
66
  },
67
+ [getFullscreenElement, updateFullscreenStatus]
68
  )
69
 
70
+ useEffect(() => {
 
 
71
  const fullscreenChangeHandler = () => {
72
+ requestAnimationFrame(updateFullscreenStatus)
73
  }
74
 
75
  if (typeof document !== 'undefined') {
76
  document.addEventListener('fullscreenchange', fullscreenChangeHandler)
77
 
78
+ // Initial status update
79
+ updateFullscreenStatus()
80
 
81
  return () => {
82
  document.removeEventListener(
83
  'fullscreenchange',
84
  fullscreenChangeHandler
85
  )
 
86
  }
87
  }
88
  }, [updateFullscreenStatus])
 
91
  }
92
 
93
  function getBrowserFullscreenElementProp(): keyof FullscreenElement {
94
+ if (typeof document === 'undefined') return 'fullscreenElement'
95
+
96
+ const props: (keyof FullscreenElement)[] = [
97
+ 'fullscreenElement',
98
+ 'mozFullScreenElement',
99
+ 'msFullscreenElement',
100
+ 'webkitFullscreenElement',
101
+ ]
102
+
103
+ for (const prop of props) {
104
+ if (prop in document) {
105
+ return prop
106
+ }
107
  }
108
+
109
+ return 'fullscreenElement'
110
  }
src/services/windows/useWindows.ts CHANGED
@@ -3,13 +3,12 @@ import { create } from 'zustand'
3
  import { WindowsStore, WindowState } from './types'
4
  import { useCallback } from 'react'
5
 
6
- // Create the Zustand store
7
  export const useWindows = create<WindowsStore>((set, get) => ({
8
  windows: {},
9
  getNextPosition: (width: number, height: number) => {
10
  const state = get()
11
  const existingWindows = Object.values(state.windows)
12
- const defaultOffset = 30 // Offset for cascading windows
13
  let newX = 0
14
  let newY = 0
15
  let maxIterations = 100 // Prevent infinite loop
@@ -73,7 +72,9 @@ export const useWindows = create<WindowsStore>((set, get) => ({
73
  set((state) => ({
74
  windows: {
75
  ...state.windows,
76
- [id]: { ...state.windows[id], ...updates },
 
 
77
  },
78
  })),
79
  removeWindow: (id) =>
 
3
  import { WindowsStore, WindowState } from './types'
4
  import { useCallback } from 'react'
5
 
 
6
  export const useWindows = create<WindowsStore>((set, get) => ({
7
  windows: {},
8
  getNextPosition: (width: number, height: number) => {
9
  const state = get()
10
  const existingWindows = Object.values(state.windows)
11
+ const defaultOffset = 100 // Offset for cascading windows
12
  let newX = 0
13
  let newY = 0
14
  let maxIterations = 100 // Prevent infinite loop
 
72
  set((state) => ({
73
  windows: {
74
  ...state.windows,
75
+ [id]: state.windows[id]
76
+ ? { ...state.windows[id], ...updates }
77
+ : state.windows[id],
78
  },
79
  })),
80
  removeWindow: (id) =>