diff --git a/package.json b/package.json index f384127..3776da2 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "@uidotdev/usehooks": "^2.4.1", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", + "cmdk": "1.0.0", "dexie": "^4.0.9", "dexie-export-import": "^4.1.2", "dexie-react-hooks": "^1.1.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a83cab0..8f82f45 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -83,6 +83,9 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 + cmdk: + specifier: 1.0.0 + version: 1.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) dexie: specifier: ^4.0.9 version: 4.0.9 @@ -1027,6 +1030,9 @@ packages: '@radix-ui/number@1.1.0': resolution: {integrity: sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==} + '@radix-ui/primitive@1.0.1': + resolution: {integrity: sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==} + '@radix-ui/primitive@1.1.0': resolution: {integrity: sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==} @@ -1082,6 +1088,15 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-compose-refs@1.0.1': + resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-compose-refs@1.1.0': resolution: {integrity: sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==} peerDependencies: @@ -1091,6 +1106,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-context@1.0.1': + resolution: {integrity: sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-context@1.1.0': resolution: {integrity: sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==} peerDependencies: @@ -1109,6 +1133,19 @@ packages: '@types/react': optional: true + '@radix-ui/react-dialog@1.0.5': + resolution: {integrity: sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-dialog@1.1.2': resolution: {integrity: sha512-Yj4dZtqa2o+kG61fzB0H2qUvmwBA2oyQroGLyNtBj1beo1khoQ3q1a2AO8rrQYjd8256CO9+N8L9tvsS+bnIyA==} peerDependencies: @@ -1131,6 +1168,19 @@ packages: '@types/react': optional: true + '@radix-ui/react-dismissable-layer@1.0.5': + resolution: {integrity: sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-dismissable-layer@1.1.1': resolution: {integrity: sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==} peerDependencies: @@ -1157,6 +1207,15 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-focus-guards@1.0.1': + resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-focus-guards@1.1.1': resolution: {integrity: sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==} peerDependencies: @@ -1166,6 +1225,19 @@ packages: '@types/react': optional: true + '@radix-ui/react-focus-scope@1.0.4': + resolution: {integrity: sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-focus-scope@1.1.0': resolution: {integrity: sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA==} peerDependencies: @@ -1179,6 +1251,15 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-id@1.0.1': + resolution: {integrity: sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-id@1.1.0': resolution: {integrity: sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==} peerDependencies: @@ -1253,6 +1334,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-portal@1.0.4': + resolution: {integrity: sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-portal@1.1.2': resolution: {integrity: sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==} peerDependencies: @@ -1266,6 +1360,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-presence@1.0.1': + resolution: {integrity: sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-presence@1.1.1': resolution: {integrity: sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==} peerDependencies: @@ -1279,6 +1386,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-primitive@1.0.3': + resolution: {integrity: sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-primitive@2.0.0': resolution: {integrity: sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==} peerDependencies: @@ -1370,6 +1490,15 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-slot@1.0.2': + resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-slot@1.1.0': resolution: {integrity: sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==} peerDependencies: @@ -1418,6 +1547,15 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-use-callback-ref@1.0.1': + resolution: {integrity: sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-use-callback-ref@1.1.0': resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==} peerDependencies: @@ -1427,6 +1565,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-controllable-state@1.0.1': + resolution: {integrity: sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-use-controllable-state@1.1.0': resolution: {integrity: sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==} peerDependencies: @@ -1436,6 +1583,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-escape-keydown@1.0.3': + resolution: {integrity: sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-use-escape-keydown@1.1.0': resolution: {integrity: sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==} peerDependencies: @@ -1445,6 +1601,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-layout-effect@1.0.1': + resolution: {integrity: sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-use-layout-effect@1.1.0': resolution: {integrity: sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==} peerDependencies: @@ -1998,6 +2163,12 @@ packages: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} + cmdk@1.0.0: + resolution: {integrity: sha512-gDzVf0a09TvoJ5jnuPvygTB77+XdOSwEmJ88L6XPFPlv7T3RxbP9jgenfylrAMD0+Le1aO0nVjQUzl2g+vjz5Q==} + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + code-block-writer@12.0.0: resolution: {integrity: sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w==} @@ -3172,6 +3343,16 @@ packages: '@types/react': optional: true + react-remove-scroll@2.5.5: + resolution: {integrity: sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + react-remove-scroll@2.6.0: resolution: {integrity: sha512-I2U4JVEsQenxDAKaVa3VZ/JeJZe0/2DxPWL8Tj8yLKctQJQiZM52pn/GWFpSp8dftjM3pSAHVJZscAnC/y+ySQ==} engines: {node: '>=10'} @@ -4759,6 +4940,10 @@ snapshots: '@radix-ui/number@1.1.0': {} + '@radix-ui/primitive@1.0.1': + dependencies: + '@babel/runtime': 7.26.0 + '@radix-ui/primitive@1.1.0': {} '@radix-ui/react-alert-dialog@1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': @@ -4812,12 +4997,26 @@ snapshots: '@types/react': 18.3.12 '@types/react-dom': 18.3.1 + '@radix-ui/react-compose-refs@1.0.1(@types/react@18.3.12)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.0 + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 + '@radix-ui/react-compose-refs@1.1.0(@types/react@18.3.12)(react@18.3.1)': dependencies: react: 18.3.1 optionalDependencies: '@types/react': 18.3.12 + '@radix-ui/react-context@1.0.1(@types/react@18.3.12)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.0 + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 + '@radix-ui/react-context@1.1.0(@types/react@18.3.12)(react@18.3.1)': dependencies: react: 18.3.1 @@ -4830,6 +5029,29 @@ snapshots: optionalDependencies: '@types/react': 18.3.12 + '@radix-ui/react-dialog@1.0.5(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.0 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-context': 1.0.1(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.0.5(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.0.1(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.0.4(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.0.1(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-portal': 1.0.4(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.0.2(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.3.12)(react@18.3.1) + aria-hidden: 1.2.4 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.5.5(@types/react@18.3.12)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 + '@radix-ui/react-dialog@1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.0 @@ -4858,6 +5080,20 @@ snapshots: optionalDependencies: '@types/react': 18.3.12 + '@radix-ui/react-dismissable-layer@1.0.5(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.0 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-use-escape-keydown': 1.0.3(@types/react@18.3.12)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 + '@radix-ui/react-dismissable-layer@1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.0 @@ -4886,12 +5122,31 @@ snapshots: '@types/react': 18.3.12 '@types/react-dom': 18.3.1 + '@radix-ui/react-focus-guards@1.0.1(@types/react@18.3.12)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.0 + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 + '@radix-ui/react-focus-guards@1.1.1(@types/react@18.3.12)(react@18.3.1)': dependencies: react: 18.3.1 optionalDependencies: '@types/react': 18.3.12 + '@radix-ui/react-focus-scope@1.0.4(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.0 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.3.12)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 + '@radix-ui/react-focus-scope@1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.12)(react@18.3.1) @@ -4903,6 +5158,14 @@ snapshots: '@types/react': 18.3.12 '@types/react-dom': 18.3.1 + '@radix-ui/react-id@1.0.1(@types/react@18.3.12)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.0 + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.3.12)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 + '@radix-ui/react-id@1.1.0(@types/react@18.3.12)(react@18.3.1)': dependencies: '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.12)(react@18.3.1) @@ -5004,6 +5267,16 @@ snapshots: '@types/react': 18.3.12 '@types/react-dom': 18.3.1 + '@radix-ui/react-portal@1.0.4(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.0 + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 + '@radix-ui/react-portal@1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -5014,6 +5287,17 @@ snapshots: '@types/react': 18.3.12 '@types/react-dom': 18.3.1 + '@radix-ui/react-presence@1.0.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.0 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.3.12)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 + '@radix-ui/react-presence@1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.12)(react@18.3.1) @@ -5024,6 +5308,16 @@ snapshots: '@types/react': 18.3.12 '@types/react-dom': 18.3.1 + '@radix-ui/react-primitive@1.0.3(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.0 + '@radix-ui/react-slot': 1.0.2(@types/react@18.3.12)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 + '@radix-ui/react-primitive@2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-slot': 1.1.0(@types/react@18.3.12)(react@18.3.1) @@ -5134,6 +5428,14 @@ snapshots: '@types/react': 18.3.12 '@types/react-dom': 18.3.1 + '@radix-ui/react-slot@1.0.2(@types/react@18.3.12)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.0 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.12)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 + '@radix-ui/react-slot@1.1.0(@types/react@18.3.12)(react@18.3.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.12)(react@18.3.1) @@ -5196,12 +5498,27 @@ snapshots: '@types/react': 18.3.12 '@types/react-dom': 18.3.1 + '@radix-ui/react-use-callback-ref@1.0.1(@types/react@18.3.12)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.0 + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 + '@radix-ui/react-use-callback-ref@1.1.0(@types/react@18.3.12)(react@18.3.1)': dependencies: react: 18.3.1 optionalDependencies: '@types/react': 18.3.12 + '@radix-ui/react-use-controllable-state@1.0.1(@types/react@18.3.12)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.0 + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.3.12)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 + '@radix-ui/react-use-controllable-state@1.1.0(@types/react@18.3.12)(react@18.3.1)': dependencies: '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.12)(react@18.3.1) @@ -5209,6 +5526,14 @@ snapshots: optionalDependencies: '@types/react': 18.3.12 + '@radix-ui/react-use-escape-keydown@1.0.3(@types/react@18.3.12)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.0 + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.3.12)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 + '@radix-ui/react-use-escape-keydown@1.1.0(@types/react@18.3.12)(react@18.3.1)': dependencies: '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.12)(react@18.3.1) @@ -5216,6 +5541,13 @@ snapshots: optionalDependencies: '@types/react': 18.3.12 + '@radix-ui/react-use-layout-effect@1.0.1(@types/react@18.3.12)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.0 + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 + '@radix-ui/react-use-layout-effect@1.1.0(@types/react@18.3.12)(react@18.3.1)': dependencies: react: 18.3.1 @@ -5795,6 +6127,16 @@ snapshots: clsx@2.1.1: {} + cmdk@1.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@radix-ui/react-dialog': 1.0.5(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + code-block-writer@12.0.0: {} color-convert@1.9.3: @@ -6925,6 +7267,17 @@ snapshots: optionalDependencies: '@types/react': 18.3.12 + react-remove-scroll@2.5.5(@types/react@18.3.12)(react@18.3.1): + dependencies: + react: 18.3.1 + react-remove-scroll-bar: 2.3.6(@types/react@18.3.12)(react@18.3.1) + react-style-singleton: 2.2.1(@types/react@18.3.12)(react@18.3.1) + tslib: 2.8.0 + use-callback-ref: 1.3.2(@types/react@18.3.12)(react@18.3.1) + use-sidecar: 1.1.2(@types/react@18.3.12)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + react-remove-scroll@2.6.0(@types/react@18.3.12)(react@18.3.1): dependencies: react: 18.3.1 diff --git a/src/components/command-palette/command-palette.tsx b/src/components/command-palette/command-palette.tsx new file mode 100644 index 0000000..3e31676 --- /dev/null +++ b/src/components/command-palette/command-palette.tsx @@ -0,0 +1,43 @@ +import { useShortcut } from "@/hooks/use-shortcut"; +import { useState } from "react"; +import { CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandShortcut } from "../ui"; +import { Command, getCategorizedCommands } from "@/lib/commands"; +import { Shortcut } from "@/lib/settings"; +import { ShortcutText } from "../common/shortcut-text"; + +const hiddenCommands: Shortcut[] = ["openCommandPalette"]; + +export const CommandPalette = () => { + const [isOpen, setOpen] = useState(false); + const commands = getCategorizedCommands(); + useShortcut("openCommandPalette", () => { + setOpen((current) => !current); + }) + + const executeCommand = (command: Command) => { + setOpen(false); + command.execute(); + } + + return ( + + + + No results found. + {Object.entries(commands).map(([category, commands]) => ( + + {commands.filter( + command => !hiddenCommands.includes(command.shortcut) + ).map(command => ( + executeCommand(command)}> + + {command.title} + + + ))} + + ))} + + + ) +} \ No newline at end of file diff --git a/src/components/settings/content/shortcuts-settings/shortcut-row.tsx b/src/components/settings/content/shortcuts-settings/shortcut-row.tsx index cf930a7..3bf64d3 100644 --- a/src/components/settings/content/shortcuts-settings/shortcut-row.tsx +++ b/src/components/settings/content/shortcuts-settings/shortcut-row.tsx @@ -1,6 +1,6 @@ import { Button } from "@/components/ui"; import { TableCell, TableRow } from "@/components/ui/table"; -import { settings, Settings } from "@/lib/settings"; +import { settings, Shortcut } from "@/lib/settings"; import { replaceShortcutSymbols } from "@/lib/utils"; import clsx from "clsx"; import { useAtom } from "jotai/react"; @@ -12,7 +12,7 @@ import { useRecordHotkeys } from "react-hotkeys-hook"; type ShortcutRowProps = { title: string; description?: string; - settingsKey: keyof Settings["shortcuts"]; + settingsKey: Shortcut; } export const ShortcutRow = ({ title, description, settingsKey }: ShortcutRowProps) => { diff --git a/src/components/settings/content/shortcuts-settings/shortcuts-settings-content.tsx b/src/components/settings/content/shortcuts-settings/shortcuts-settings-content.tsx index dc20025..51defea 100644 --- a/src/components/settings/content/shortcuts-settings/shortcuts-settings-content.tsx +++ b/src/components/settings/content/shortcuts-settings/shortcuts-settings-content.tsx @@ -2,22 +2,16 @@ import { Keyboard } from "lucide-react"; import { SettingsNavbarItem } from "../../types"; import { Table, TableBody } from "@/components/ui/table"; import { ShortcutRow } from "./shortcut-row"; -import { productName } from "@/lib/constants"; +import { commandDefinitions } from "@/lib/commands"; +import { Shortcut } from "@/lib/settings"; const ShortcutsSettingsContent = () => { return ( - - - - - - - - - - + {Object.entries(commandDefinitions).map(([key, { title, description }]) => ( + + ))}
); diff --git a/src/components/ui/command.tsx b/src/components/ui/command.tsx new file mode 100644 index 0000000..f0e4e3f --- /dev/null +++ b/src/components/ui/command.tsx @@ -0,0 +1,151 @@ +import * as React from "react" +import { type DialogProps } from "@radix-ui/react-dialog" +import { Command as CommandPrimitive } from "cmdk" +import { Search } from "lucide-react" + +import { cn } from "@/lib/utils" +import { Dialog, DialogContent } from "@/components/ui/dialog" + +const Command = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +Command.displayName = CommandPrimitive.displayName + +const CommandDialog = ({ children, ...props }: DialogProps) => { + return ( + + + + {children} + + + + ) +} + +const CommandInput = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( +
+ + +
+)) + +CommandInput.displayName = CommandPrimitive.Input.displayName + +const CommandList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) + +CommandList.displayName = CommandPrimitive.List.displayName + +const CommandEmpty = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>((props, ref) => ( + +)) + +CommandEmpty.displayName = CommandPrimitive.Empty.displayName + +const CommandGroup = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) + +CommandGroup.displayName = CommandPrimitive.Group.displayName + +const CommandSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +CommandSeparator.displayName = CommandPrimitive.Separator.displayName + +const CommandItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) + +CommandItem.displayName = CommandPrimitive.Item.displayName + +const CommandShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ) +} +CommandShortcut.displayName = "CommandShortcut" + +export { + Command, + CommandDialog, + CommandInput, + CommandList, + CommandEmpty, + CommandGroup, + CommandItem, + CommandShortcut, + CommandSeparator, +} diff --git a/src/components/ui/index.tsx b/src/components/ui/index.tsx index 0c946b8..d1ed051 100644 --- a/src/components/ui/index.tsx +++ b/src/components/ui/index.tsx @@ -18,3 +18,4 @@ export * from "./sonner"; export * from "./switch"; export * from "./textarea"; export * from "./tooltip"; +export * from "./command"; diff --git a/src/hooks/use-shortcut.tsx b/src/hooks/use-shortcut.tsx index 3802ddb..caeb711 100644 --- a/src/hooks/use-shortcut.tsx +++ b/src/hooks/use-shortcut.tsx @@ -1,8 +1,13 @@ -import { settings, Settings } from "@/lib/settings"; +import { registerCommand } from "@/lib/commands"; +import { settings, Shortcut } from "@/lib/settings"; import { useAtom } from "jotai/react"; import { useHotkeys } from "react-hotkeys-hook"; -export const useShortcut = (settingsKey: keyof Settings["shortcuts"], callback: () => void) => { +export const useShortcut = (settingsKey: Shortcut, callback: () => void) => { const [shortcut] = useAtom(settings.shortcuts[settingsKey]); + registerCommand({ + shortcut: settingsKey, + execute: callback, + }); useHotkeys(shortcut, callback, { enableOnFormTags: ["INPUT", "TEXTAREA"], preventDefault: true }); } \ No newline at end of file diff --git a/src/layouts/image-list-layout.tsx b/src/layouts/image-list-layout.tsx index bdca8d7..9da2521 100644 --- a/src/layouts/image-list-layout.tsx +++ b/src/layouts/image-list-layout.tsx @@ -22,6 +22,7 @@ import { useImageNavigation, useImageNavigationHotkeys } from "../hooks/provider import { NoImageSelected } from "@/components/common/no-image-selected"; import { SettingsDialog } from "@/components/settings/settings-dialog"; import { ExportDialog } from "@/components/export/export-dialog"; +import { CommandPalette } from "@/components/command-palette/command-palette"; type ImageListLayoutProps = { children: ReactNode; @@ -39,6 +40,7 @@ export const ImageListLayout = ({ +
diff --git a/src/lib/commands.ts b/src/lib/commands.ts new file mode 100644 index 0000000..76310be --- /dev/null +++ b/src/lib/commands.ts @@ -0,0 +1,155 @@ +import { FC } from "react"; +import { productName } from "./constants"; +import { Shortcut } from "./settings"; +import { + ClipboardCopy, + ClipboardPaste, + ClipboardPlus, + Command, + Download, + ImageDown, + ImageUp, + Save, + Search, + Settings, + Trash, +} from "lucide-react"; + +export type CommandHandler = { + shortcut: Shortcut; + execute: () => void; +}; + +export type CommandDefinition = { + icon: FC; + title: string; + description: string; + category: CommandCategory; +}; + +export type Command = CommandHandler & CommandDefinition; +export type CommandCategory = + | "General" + | "Image" + | "Export" + | "Caption" + | "Categories"; + +export const commandDefinitions: Record = { + openSettings: { + icon: Settings, + title: "Open settings", + description: "Opens or closes this settings dialog", + category: "General", + }, + openCommandPalette: { + icon: Command, + title: "Open command palette", + description: "Opens the command palette to search for commands", + category: "General", + }, + applySuggestionModifier: { + icon: ClipboardPlus, + title: "Apply AI suggestion modifier", + description: + "Configures the modifier key that should be pressed along a number key (1-9) to apply an AI caption part suggestion", + category: "Caption", + }, + saveCaption: { + icon: Save, + title: "Save caption", + description: `${productName} saves automatically when necessary, but sometimes it just feels satisfying to mash that Save button.`, + category: "Caption", + }, + previousImage: { + icon: ImageUp, + title: "Previous image", + description: "Load the previous image from the image sidebar", + category: "Image", + }, + nextImage: { + icon: ImageDown, + title: "Next image", + description: "Load the next image from the image sidebar", + category: "Image", + }, + startExport: { + icon: Download, + title: "Start export", + description: "Immediately starts a new export", + category: "Export", + }, + clearCaption: { + icon: Trash, + title: "Clear current caption", + description: "Removes the caption of the currently open image", + category: "Caption", + }, + copyCaptionParts: { + icon: ClipboardCopy, + title: "Copy caption parts", + description: + "Copies the currently open picture's caption parts into an internal clipboard", + category: "Caption", + }, + pasteCaptionParts: { + icon: ClipboardPaste, + title: "Paste caption parts", + description: + "Adds the caption parts on the internal clipboard into the currently open image's caption", + category: "Caption", + }, + searchCurrentCaption: { + icon: Search, + title: "Search in current caption", + description: + "Shows a search dialog to find text in the currently open caption", + category: "Caption", + }, + saveCategories: { + icon: Save, + title: "Save categories", + description: "Save the current categories", + category: "Categories", + }, + clearCategories: { + icon: Trash, + title: "Clear categories", + description: "Clear all categories", + category: "Categories", + }, +}; +const commandRegistry: Partial> = {}; + +export const registerCommand = (handler: CommandHandler) => { + commandRegistry[handler.shortcut] = handler; +}; + +export const getCommands = (): Command[] => + Object.values(commandRegistry).map((command) => ({ + ...command, + ...commandDefinitions[command.shortcut], + })); + +export const getCategorizedCommands = (): Record< + CommandCategory, + Command[] +> => { + const categorizedCommands: Record = { + General: [], + Image: [], + Export: [], + Caption: [], + Categories: [], + }; + + for (const command of getCommands()) { + categorizedCommands[command.category].push(command); + } + + return categorizedCommands; +}; + +export const getCommandDefinition = (shortcut: Shortcut): CommandDefinition => { + return commandDefinitions[shortcut]; +}; diff --git a/src/lib/settings.ts b/src/lib/settings.ts index b564e49..267e17e 100644 --- a/src/lib/settings.ts +++ b/src/lib/settings.ts @@ -25,6 +25,10 @@ export const settings = { "settings.shortcuts.openSettings", "mod+shift+p" ), + openCommandPalette: atomWithStorage( + "settings.shortcuts.openCommandPalette", + "mod+k" + ), applySuggestionModifier: atomWithStorage( "settings.shortcuts.applySuggestionModifier", "mod" @@ -132,6 +136,7 @@ export const settings = { } as const; export type Settings = typeof settings; +export type Shortcut = keyof Settings["shortcuts"]; export const resetLocalStorage = () => { for (const key in localStorage) {