diff --git a/package.json b/package.json index 9405e5ece..2aa9c5c50 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "license": "MIT", "dependencies": { "@fontsource/roboto-mono": "5.0.8", - "@reach/menu-button": "0.18.0", + "@radix-ui/react-menubar": "^1.0.4", "@reach/tooltip": "0.18.0", "@reach/visually-hidden": "0.18.0", "@tanstack/react-query": "4.35.3", @@ -59,6 +59,7 @@ "react-tiny-fab": "4.0.4", "react-window": "^1.8.9", "reselect": "4.1.8", + "sonner": "^1.0.3", "tslib": "2.6.2", "use-asset": "1.0.4", "workbox-core": "7.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c72b94992..b39b1ccbd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,9 +13,9 @@ dependencies: '@fontsource/roboto-mono': specifier: 5.0.8 version: 5.0.8 - '@reach/menu-button': - specifier: 0.18.0 - version: 0.18.0(react-dom@18.2.0)(react-is@17.0.2)(react@18.2.0) + '@radix-ui/react-menubar': + specifier: ^1.0.4 + version: 1.0.4(@types/react-dom@18.2.8)(@types/react@18.2.23)(react-dom@18.2.0)(react@18.2.0) '@reach/tooltip': specifier: 0.18.0 version: 0.18.0(react-dom@18.2.0)(react@18.2.0) @@ -103,6 +103,9 @@ dependencies: reselect: specifier: 4.1.8 version: 4.1.8 + sonner: + specifier: ^1.0.3 + version: 1.0.3(react-dom@18.2.0)(react@18.2.0) tslib: specifier: 2.6.2 version: 2.6.2 @@ -1417,7 +1420,6 @@ packages: engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.14.0 - dev: true /@babel/template@7.22.15: resolution: {integrity: sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==} @@ -1704,6 +1706,34 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true + /@floating-ui/core@1.5.0: + resolution: {integrity: sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg==} + dependencies: + '@floating-ui/utils': 0.1.6 + dev: false + + /@floating-ui/dom@1.5.3: + resolution: {integrity: sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==} + dependencies: + '@floating-ui/core': 1.5.0 + '@floating-ui/utils': 0.1.6 + dev: false + + /@floating-ui/react-dom@2.0.2(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-5qhlDvjaLmAst/rKb3VdlCinwTF4EYMiVxuuc/HVUjs46W0zgtbMmAZ1UTsDrRTxRmUEzl92mOtWbeeXL26lSQ==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + dependencies: + '@floating-ui/dom': 1.5.3 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@floating-ui/utils@0.1.6: + resolution: {integrity: sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==} + dev: false + /@fontsource/inter@5.0.8: resolution: {integrity: sha512-28knWH1BfOiRalfLs90U4sge5mpQ8ZH6FS0PTT+IZMKrZ7wNHDHRuKa1kQJg+uHcc6axBppnxll+HXM4c7zo/Q==} dev: true @@ -1794,84 +1824,497 @@ packages: fastq: 1.15.0 dev: true - /@reach/auto-id@0.18.0(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-XwY1IwhM7mkHZFghhjiqjQ6dstbOdpbFLdggeke75u8/8icT8uEHLbovFUgzKjy9qPvYwZIB87rLiR8WdtOXCg==} + /@radix-ui/primitive@1.0.1: + resolution: {integrity: sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==} + dependencies: + '@babel/runtime': 7.23.1 + dev: false + + /@radix-ui/react-arrow@1.0.3(@types/react-dom@18.2.8)(@types/react@18.2.23)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA==} peerDependencies: - react: ^16.8.0 || 17.x - react-dom: ^16.8.0 || 17.x + '@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 dependencies: - '@reach/utils': 0.18.0(react-dom@18.2.0)(react@18.2.0) + '@babel/runtime': 7.23.1 + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.8)(@types/react@18.2.23)(react-dom@18.2.0)(react@18.2.0) + '@types/react': 18.2.23 + '@types/react-dom': 18.2.8 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: false - /@reach/descendants@0.18.0(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-GXUxnM6CfrX5URdnipPIl3Tlc6geuz4xb4n61y4tVWXQX1278Ra9Jz9DMRN8x4wheHAysvrYwnR/SzAlxQzwtA==} + /@radix-ui/react-collection@1.0.3(@types/react-dom@18.2.8)(@types/react@18.2.23)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==} peerDependencies: - react: ^16.8.0 || 17.x - react-dom: ^16.8.0 || 17.x + '@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 dependencies: - '@reach/utils': 0.18.0(react-dom@18.2.0)(react@18.2.0) + '@babel/runtime': 7.23.1 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.23)(react@18.2.0) + '@radix-ui/react-context': 1.0.1(@types/react@18.2.23)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.8)(@types/react@18.2.23)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-slot': 1.0.2(@types/react@18.2.23)(react@18.2.0) + '@types/react': 18.2.23 + '@types/react-dom': 18.2.8 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: false - /@reach/dropdown@0.18.0(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-LriXdVgxJoUhIQfS2r2DHYv3X6fHyplYxa9FmSwQIMXdESpE/P9Zsb1pVEObcNf3ZQBrl0L1bl/5rk7SpK7qfA==} + /@radix-ui/react-compose-refs@1.0.1(@types/react@18.2.23)(react@18.2.0): + resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==} peerDependencies: - react: ^16.8.0 || 17.x - react-dom: ^16.8.0 || 17.x + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true dependencies: - '@reach/auto-id': 0.18.0(react-dom@18.2.0)(react@18.2.0) - '@reach/descendants': 0.18.0(react-dom@18.2.0)(react@18.2.0) - '@reach/polymorphic': 0.18.0(react@18.2.0) - '@reach/popover': 0.18.0(react-dom@18.2.0)(react@18.2.0) - '@reach/utils': 0.18.0(react-dom@18.2.0)(react@18.2.0) + '@babel/runtime': 7.23.1 + '@types/react': 18.2.23 + react: 18.2.0 + dev: false + + /@radix-ui/react-context@1.0.1(@types/react@18.2.23)(react@18.2.0): + resolution: {integrity: sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.23.1 + '@types/react': 18.2.23 + react: 18.2.0 + dev: false + + /@radix-ui/react-direction@1.0.1(@types/react@18.2.23)(react@18.2.0): + resolution: {integrity: sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.23.1 + '@types/react': 18.2.23 + react: 18.2.0 + dev: false + + /@radix-ui/react-dismissable-layer@1.0.5(@types/react-dom@18.2.8)(@types/react@18.2.23)(react-dom@18.2.0)(react@18.2.0): + 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 + dependencies: + '@babel/runtime': 7.23.1 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.23)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.8)(@types/react@18.2.23)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.23)(react@18.2.0) + '@radix-ui/react-use-escape-keydown': 1.0.3(@types/react@18.2.23)(react@18.2.0) + '@types/react': 18.2.23 + '@types/react-dom': 18.2.8 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: false - /@reach/menu-button@0.18.0(react-dom@18.2.0)(react-is@17.0.2)(react@18.2.0): - resolution: {integrity: sha512-v1lj5rYSpavOKI4ipXj8OfvQmvVNAYXCv+UcltRkjOcWEKWADUUKkGX55wiUhsCsTGCJ7lGYz5LqOZrn3LP6PQ==} + /@radix-ui/react-focus-guards@1.0.1(@types/react@18.2.23)(react@18.2.0): + resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==} peerDependencies: - react: ^16.8.0 || 17.x - react-dom: ^16.8.0 || 17.x - react-is: ^16.8.0 || 17.x + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true dependencies: - '@reach/dropdown': 0.18.0(react-dom@18.2.0)(react@18.2.0) - '@reach/polymorphic': 0.18.0(react@18.2.0) - '@reach/popover': 0.18.0(react-dom@18.2.0)(react@18.2.0) - '@reach/utils': 0.18.0(react-dom@18.2.0)(react@18.2.0) + '@babel/runtime': 7.23.1 + '@types/react': 18.2.23 + react: 18.2.0 + dev: false + + /@radix-ui/react-focus-scope@1.0.4(@types/react-dom@18.2.8)(@types/react@18.2.23)(react-dom@18.2.0)(react@18.2.0): + 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 + dependencies: + '@babel/runtime': 7.23.1 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.23)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.8)(@types/react@18.2.23)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.23)(react@18.2.0) + '@types/react': 18.2.23 + '@types/react-dom': 18.2.8 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - react-is: 17.0.2 dev: false - /@reach/observe-rect@1.2.0: - resolution: {integrity: sha512-Ba7HmkFgfQxZqqaeIWWkNK0rEhpxVQHIoVyW1YDSkGsGIXzcaW4deC8B0pZrNSSyLTdIk7y+5olKt5+g0GmFIQ==} + /@radix-ui/react-id@1.0.1(@types/react@18.2.23)(react@18.2.0): + resolution: {integrity: sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.23.1 + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.23)(react@18.2.0) + '@types/react': 18.2.23 + react: 18.2.0 dev: false - /@reach/polymorphic@0.18.0(react@18.2.0): - resolution: {integrity: sha512-N9iAjdMbE//6rryZZxAPLRorzDcGBnluf7YQij6XDLiMtfCj1noa7KyLpEc/5XCIB/EwhX3zCluFAwloBKdblA==} + /@radix-ui/react-menu@2.0.6(@types/react-dom@18.2.8)(@types/react@18.2.23)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-BVkFLS+bUC8HcImkRKPSiVumA1VPOOEC5WBMiT+QAVsPzW1FJzI9KnqgGxVDPBcql5xXrHkD3JOVoXWEXD8SYA==} peerDependencies: - react: ^16.8.0 || 17.x + '@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 + dependencies: + '@babel/runtime': 7.23.1 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-collection': 1.0.3(@types/react-dom@18.2.8)(@types/react@18.2.23)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.23)(react@18.2.0) + '@radix-ui/react-context': 1.0.1(@types/react@18.2.23)(react@18.2.0) + '@radix-ui/react-direction': 1.0.1(@types/react@18.2.23)(react@18.2.0) + '@radix-ui/react-dismissable-layer': 1.0.5(@types/react-dom@18.2.8)(@types/react@18.2.23)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-focus-guards': 1.0.1(@types/react@18.2.23)(react@18.2.0) + '@radix-ui/react-focus-scope': 1.0.4(@types/react-dom@18.2.8)(@types/react@18.2.23)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-id': 1.0.1(@types/react@18.2.23)(react@18.2.0) + '@radix-ui/react-popper': 1.1.3(@types/react-dom@18.2.8)(@types/react@18.2.23)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-portal': 1.0.4(@types/react-dom@18.2.8)(@types/react@18.2.23)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.8)(@types/react@18.2.23)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.8)(@types/react@18.2.23)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-roving-focus': 1.0.4(@types/react-dom@18.2.8)(@types/react@18.2.23)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-slot': 1.0.2(@types/react@18.2.23)(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.23)(react@18.2.0) + '@types/react': 18.2.23 + '@types/react-dom': 18.2.8 + aria-hidden: 1.2.3 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-remove-scroll: 2.5.5(@types/react@18.2.23)(react@18.2.0) + dev: false + + /@radix-ui/react-menubar@1.0.4(@types/react-dom@18.2.8)(@types/react@18.2.23)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-bHgUo9gayKZfaQcWSSLr++LyS0rgh+MvD89DE4fJ6TkGHvjHgPaBZf44hdka7ogOxIOdj9163J+5xL2Dn4qzzg==} + 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 + dependencies: + '@babel/runtime': 7.23.1 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-collection': 1.0.3(@types/react-dom@18.2.8)(@types/react@18.2.23)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.23)(react@18.2.0) + '@radix-ui/react-context': 1.0.1(@types/react@18.2.23)(react@18.2.0) + '@radix-ui/react-direction': 1.0.1(@types/react@18.2.23)(react@18.2.0) + '@radix-ui/react-id': 1.0.1(@types/react@18.2.23)(react@18.2.0) + '@radix-ui/react-menu': 2.0.6(@types/react-dom@18.2.8)(@types/react@18.2.23)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.8)(@types/react@18.2.23)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-roving-focus': 1.0.4(@types/react-dom@18.2.8)(@types/react@18.2.23)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.23)(react@18.2.0) + '@types/react': 18.2.23 + '@types/react-dom': 18.2.8 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@radix-ui/react-popper@1.1.3(@types/react-dom@18.2.8)(@types/react@18.2.23)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w==} + 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 + dependencies: + '@babel/runtime': 7.23.1 + '@floating-ui/react-dom': 2.0.2(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-arrow': 1.0.3(@types/react-dom@18.2.8)(@types/react@18.2.23)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.23)(react@18.2.0) + '@radix-ui/react-context': 1.0.1(@types/react@18.2.23)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.8)(@types/react@18.2.23)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.23)(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.23)(react@18.2.0) + '@radix-ui/react-use-rect': 1.0.1(@types/react@18.2.23)(react@18.2.0) + '@radix-ui/react-use-size': 1.0.1(@types/react@18.2.23)(react@18.2.0) + '@radix-ui/rect': 1.0.1 + '@types/react': 18.2.23 + '@types/react-dom': 18.2.8 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@radix-ui/react-portal@1.0.4(@types/react-dom@18.2.8)(@types/react@18.2.23)(react-dom@18.2.0)(react@18.2.0): + 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 + dependencies: + '@babel/runtime': 7.23.1 + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.8)(@types/react@18.2.23)(react-dom@18.2.0)(react@18.2.0) + '@types/react': 18.2.23 + '@types/react-dom': 18.2.8 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@radix-ui/react-presence@1.0.1(@types/react-dom@18.2.8)(@types/react@18.2.23)(react-dom@18.2.0)(react@18.2.0): + 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 dependencies: + '@babel/runtime': 7.23.1 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.23)(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.23)(react@18.2.0) + '@types/react': 18.2.23 + '@types/react-dom': 18.2.8 react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) dev: false - /@reach/popover@0.18.0(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-mpnWWn4w74L2U7fcneVdA6Fz3yKWNdZIRMoK8s6H7F8U2dLM/qN7AjzjEBqi6LXKb3Uf1ge4KHSbMixW0BygJQ==} + /@radix-ui/react-primitive@1.0.3(@types/react-dom@18.2.8)(@types/react@18.2.23)(react-dom@18.2.0)(react@18.2.0): + 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 + dependencies: + '@babel/runtime': 7.23.1 + '@radix-ui/react-slot': 1.0.2(@types/react@18.2.23)(react@18.2.0) + '@types/react': 18.2.23 + '@types/react-dom': 18.2.8 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@radix-ui/react-roving-focus@1.0.4(@types/react-dom@18.2.8)(@types/react@18.2.23)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==} + 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 + dependencies: + '@babel/runtime': 7.23.1 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-collection': 1.0.3(@types/react-dom@18.2.8)(@types/react@18.2.23)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.23)(react@18.2.0) + '@radix-ui/react-context': 1.0.1(@types/react@18.2.23)(react@18.2.0) + '@radix-ui/react-direction': 1.0.1(@types/react@18.2.23)(react@18.2.0) + '@radix-ui/react-id': 1.0.1(@types/react@18.2.23)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.8)(@types/react@18.2.23)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.23)(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.23)(react@18.2.0) + '@types/react': 18.2.23 + '@types/react-dom': 18.2.8 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@radix-ui/react-slot@1.0.2(@types/react@18.2.23)(react@18.2.0): + resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.23.1 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.23)(react@18.2.0) + '@types/react': 18.2.23 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-callback-ref@1.0.1(@types/react@18.2.23)(react@18.2.0): + resolution: {integrity: sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.23.1 + '@types/react': 18.2.23 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-controllable-state@1.0.1(@types/react@18.2.23)(react@18.2.0): + resolution: {integrity: sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.23.1 + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.23)(react@18.2.0) + '@types/react': 18.2.23 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-escape-keydown@1.0.3(@types/react@18.2.23)(react@18.2.0): + resolution: {integrity: sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.23.1 + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.23)(react@18.2.0) + '@types/react': 18.2.23 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-layout-effect@1.0.1(@types/react@18.2.23)(react@18.2.0): + resolution: {integrity: sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.23.1 + '@types/react': 18.2.23 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-rect@1.0.1(@types/react@18.2.23)(react@18.2.0): + resolution: {integrity: sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.23.1 + '@radix-ui/rect': 1.0.1 + '@types/react': 18.2.23 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-size@1.0.1(@types/react@18.2.23)(react@18.2.0): + resolution: {integrity: sha512-ibay+VqrgcaI6veAojjofPATwledXiSmX+C0KrBk/xgpX9rBzPV3OsfwlhQdUOFbh+LKQorLYT+xTXW9V8yd0g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.23.1 + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.23)(react@18.2.0) + '@types/react': 18.2.23 + react: 18.2.0 + dev: false + + /@radix-ui/rect@1.0.1: + resolution: {integrity: sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==} + dependencies: + '@babel/runtime': 7.23.1 + dev: false + + /@reach/auto-id@0.18.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-XwY1IwhM7mkHZFghhjiqjQ6dstbOdpbFLdggeke75u8/8icT8uEHLbovFUgzKjy9qPvYwZIB87rLiR8WdtOXCg==} peerDependencies: react: ^16.8.0 || 17.x react-dom: ^16.8.0 || 17.x dependencies: - '@reach/polymorphic': 0.18.0(react@18.2.0) - '@reach/portal': 0.18.0(react-dom@18.2.0)(react@18.2.0) - '@reach/rect': 0.18.0(react-dom@18.2.0)(react@18.2.0) '@reach/utils': 0.18.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - tabbable: 5.3.3 + dev: false + + /@reach/observe-rect@1.2.0: + resolution: {integrity: sha512-Ba7HmkFgfQxZqqaeIWWkNK0rEhpxVQHIoVyW1YDSkGsGIXzcaW4deC8B0pZrNSSyLTdIk7y+5olKt5+g0GmFIQ==} + dev: false + + /@reach/polymorphic@0.18.0(react@18.2.0): + resolution: {integrity: sha512-N9iAjdMbE//6rryZZxAPLRorzDcGBnluf7YQij6XDLiMtfCj1noa7KyLpEc/5XCIB/EwhX3zCluFAwloBKdblA==} + peerDependencies: + react: ^16.8.0 || 17.x + dependencies: + react: 18.2.0 dev: false /@reach/portal@0.18.0(react-dom@18.2.0)(react@18.2.0): @@ -2107,7 +2550,6 @@ packages: resolution: {integrity: sha512-bAIvO5lN/U8sPGvs1Xm61rlRHHaq5rp5N3kp9C+NJ/Q41P8iqjkXSu0+/qu8POsjH9pNWb0OYabFez7taP7omw==} dependencies: '@types/react': 18.2.23 - dev: true /@types/react-modal@3.16.1: resolution: {integrity: sha512-HsH2E3luJ2hdVOAovq93x/r+CmWqbhwT8hXW05KMp3zzbSEZsBn4egnMGU/VLwScwlCRPTyZgIEPqLycaz5EOw==} @@ -2355,6 +2797,13 @@ packages: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} dev: true + /aria-hidden@1.2.3: + resolution: {integrity: sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ==} + engines: {node: '>=10'} + dependencies: + tslib: 2.6.2 + dev: false + /aria-query@5.1.3: resolution: {integrity: sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==} dependencies: @@ -2817,6 +3266,10 @@ packages: object-keys: 1.1.1 dev: true + /detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + dev: false + /dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -3600,6 +4053,11 @@ packages: has-symbols: 1.0.3 dev: true + /get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + dev: false + /get-own-enumerable-property-symbols@3.0.2: resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==} dev: true @@ -4658,10 +5116,6 @@ packages: /react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - /react-is@17.0.2: - resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} - dev: false - /react-lifecycles-compat@3.0.4: resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==} dev: false @@ -4686,6 +5140,41 @@ packages: engines: {node: '>=0.10.0'} dev: true + /react-remove-scroll-bar@2.3.4(@types/react@18.2.23)(react@18.2.0): + resolution: {integrity: sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==} + 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 + dependencies: + '@types/react': 18.2.23 + react: 18.2.0 + react-style-singleton: 2.2.1(@types/react@18.2.23)(react@18.2.0) + tslib: 2.6.2 + dev: false + + /react-remove-scroll@2.5.5(@types/react@18.2.23)(react@18.2.0): + 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 + dependencies: + '@types/react': 18.2.23 + react: 18.2.0 + react-remove-scroll-bar: 2.3.4(@types/react@18.2.23)(react@18.2.0) + react-style-singleton: 2.2.1(@types/react@18.2.23)(react@18.2.0) + tslib: 2.6.2 + use-callback-ref: 1.3.0(@types/react@18.2.23)(react@18.2.0) + use-sidecar: 1.1.2(@types/react@18.2.23)(react@18.2.0) + dev: false + /react-router-dom@6.16.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-aTfBLv3mk/gaKLxgRDUPbPw+s4Y/O+ma3rEN1u8EgEpLpPe6gNjIsWt9rxushMHHMb7mSwxRGdGlGdvmFsyPIg==} engines: {node: '>=14.0.0'} @@ -4709,6 +5198,23 @@ packages: react: 18.2.0 dev: false + /react-style-singleton@2.2.1(@types/react@18.2.23)(react@18.2.0): + resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==} + 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 + dependencies: + '@types/react': 18.2.23 + get-nonce: 1.0.1 + invariant: 2.2.4 + react: 18.2.0 + tslib: 2.6.2 + dev: false + /react-tabs@6.0.2(react@18.2.0): resolution: {integrity: sha512-aQXTKolnM28k3KguGDBSAbJvcowOQr23A+CUJdzJtOSDOtTwzEaJA+1U4KwhNL9+Obe+jFS7geuvA7ICQPXOnQ==} peerDependencies: @@ -5046,6 +5552,16 @@ packages: engines: {node: '>=8'} dev: true + /sonner@1.0.3(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-hBoA2zKuYW3lUnpx4K0vAn8j77YuYiwvP9sLQfieNS2pd5FkT20sMyPTDJnl9S+5T27ZJbwQRPiujwvDBwhZQg==} + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + dependencies: + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /source-map-js@1.0.2: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} @@ -5229,10 +5745,6 @@ packages: engines: {node: '>= 0.4'} dev: true - /tabbable@5.3.3: - resolution: {integrity: sha512-QD9qKY3StfbZqWOPLp0++pOrAVb/HbUi5xCc8cUo4XjP19808oaMiDzn0leBY5mCespIBM0CIZePzZjgzR83kA==} - dev: false - /temp-dir@2.0.0: resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==} engines: {node: '>=8'} @@ -5439,6 +5951,37 @@ packages: react: 18.2.0 dev: false + /use-callback-ref@1.3.0(@types/react@18.2.23)(react@18.2.0): + resolution: {integrity: sha512-3FT9PRuRdbB9HfXhEq35u4oZkvpJ5kuYbpqhCfmiZyReuRgpnhDlbr2ZEnnuS0RrJAPn6l23xjFg9kpDM+Ms7w==} + 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 + dependencies: + '@types/react': 18.2.23 + react: 18.2.0 + tslib: 2.6.2 + dev: false + + /use-sidecar@1.1.2(@types/react@18.2.23)(react@18.2.0): + resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.9.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.2.23 + detect-node-es: 1.1.0 + react: 18.2.0 + tslib: 2.6.2 + dev: false + /use-sync-external-store@1.2.0(react@18.2.0): resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} peerDependencies: diff --git a/src/api/fetch.ts b/src/api/fetch.ts new file mode 100644 index 000000000..9436b7c91 --- /dev/null +++ b/src/api/fetch.ts @@ -0,0 +1,6 @@ +export function req(url: string, init: RequestInit) { + if (import.meta.env.DEV) { + return import('./mock').then((mod) => mod.mock(url, init)); + } + return fetch(url, init); +} diff --git a/src/api/mock.ts b/src/api/mock.ts new file mode 100644 index 000000000..5381aa361 --- /dev/null +++ b/src/api/mock.ts @@ -0,0 +1,70 @@ +const MOCK_HANDLERS = [ + { + key: 'GET/', + enabled: false, + handler: (_u: string, _i: RequestInit) => { + // throw new Error(); + return deserializeError(); + // return json({ hello: 'clash' }); + }, + }, + { + key: 'GET/configs', + enabled: false, + handler: (_u: string, _i: RequestInit) => + json({ + port: 0, + 'socks-port': 7891, + 'redir-port': 0, + 'tproxy-port': 0, + 'mixed-port': 7890, + 'allow-lan': true, + 'bind-address': '*', + mode: 'rule', + 'log-level': 'info', + authentication: [], + ipv6: false, + }), + }, +]; + +export async function mock(url: string, init: RequestInit) { + const method = init.method || 'GET'; + const pathname = new URL(url).pathname; + const key = `${method}${pathname}`; + const item = MOCK_HANDLERS.find((h) => { + if (h.enabled && h.key === key) return h; + }); + if (item) { + console.warn('Using mocked API', key); + return (await item?.handler(url, init)) as Response; + } + return fetch(url, init); +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +async function json(data: T) { + await sleep(1); + return { + ok: true, + json: async () => { + await sleep(16); + return data; + }, + }; +} + +async function deserializeError() { + await sleep(1); + return { + ok: true, + json: async () => { + await sleep(16); + throw new Error(); + }, + }; +} + +function sleep(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} diff --git a/src/components/APIDiscovery.module.scss b/src/components/APIDiscovery.module.scss deleted file mode 100644 index e2161f869..000000000 --- a/src/components/APIDiscovery.module.scss +++ /dev/null @@ -1,33 +0,0 @@ -.content.content { - background: none; - position: fixed; - top: 0; - bottom: 0; - left: 0; - right: 0; - - transform: none; - padding: 0; - border-radius: 0; - - display: flex; - justify-content: center; - overflow-y: auto; -} - -.container { - position: relative; - margin-left: 20px; - margin-right: 20px; -} - -.overlay.overlay { - background-color: var(--color-background); -} - -.fixed { - position: fixed; - padding: 16px; - bottom: 0; - right: 0; -} diff --git a/src/components/APIDiscovery.tsx b/src/components/APIDiscovery.tsx deleted file mode 100644 index 2fd3c1395..000000000 --- a/src/components/APIDiscovery.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import * as React from 'react'; -import { ThemeSwitcher } from 'src/components/shared/ThemeSwitcher'; -import { DOES_NOT_SUPPORT_FETCH, errors, YacdError } from 'src/misc/errors'; -import { closeModal } from 'src/store/modals'; -import { DispatchFn, State, StateModals } from 'src/store/types'; - -import APIConfig from './APIConfig'; -import s0 from './APIDiscovery.module.scss'; -import Modal from './Modal'; -import { connect } from './StateProvider'; - -const { useCallback } = React; - -function APIDiscovery({ dispatch, modals }: { dispatch: DispatchFn; modals: StateModals }) { - if (!window.fetch) { - const { detail } = errors[DOES_NOT_SUPPORT_FETCH]; - const err = new YacdError(detail, DOES_NOT_SUPPORT_FETCH); - throw err; - } - - const closeApiConfigModal = useCallback(() => { - dispatch(closeModal('apiConfig')); - }, [dispatch]); - - return ( - -
- -
- -
- -
-
- ); -} - -const mapState = (s: State) => ({ - modals: s.modals, -}); - -export default connect(mapState)(APIDiscovery); diff --git a/src/components/Config.tsx b/src/components/Config.tsx index 181f51dc8..9dd794304 100644 --- a/src/components/Config.tsx +++ b/src/components/Config.tsx @@ -3,7 +3,7 @@ import { useAtom } from 'jotai'; import * as React from 'react'; import { LogOut } from 'react-feather'; import { useTranslation } from 'react-i18next'; -import { redirect } from 'react-router'; +import { useNavigate } from 'react-router-dom'; import { updateConfigs } from '$src/api/configs'; import * as logsApi from '$src/api/logs'; @@ -65,6 +65,7 @@ type ConfigImplProps = { }; function Config({ configs }: ConfigImplProps) { + const navigate = useNavigate(); const [latencyTestUrl, setLatencyTestUrl] = useAtom(latencyTestUrlAtom); const [selectedChartStyleIndex, setSelectedChartStyleIndex] = useAtom( selectedChartStyleIndexAtom, @@ -78,12 +79,6 @@ function Config({ configs }: ConfigImplProps) { } refConfigs.current = configs; }, [configs]); - - const openAPIConfigModal = useCallback(() => { - redirect('/backend'); - // dispatch(openModal('apiConfig')); - }, []); - const setConfigState = useCallback( (name: keyof ClashGeneralConfig, val: ClashGeneralConfig[keyof ClashGeneralConfig]) => { setConfigStateInternal({ ...configState, [name]: val }); @@ -141,12 +136,6 @@ function Config({ configs }: ConfigImplProps) { (e) => handleChangeValue(e.target), [handleChangeValue], ); - const selectChartStyleIndex = useCallback( - (idx: number) => { - setSelectedChartStyleIndex(idx); - }, - [setSelectedChartStyleIndex], - ); const handleInputOnBlur = useCallback>( (e) => { @@ -259,7 +248,7 @@ function Config({ configs }: ConfigImplProps) { OptionComponent={TrafficChartSample} optionPropsList={propsList} selectedIndex={selectedChartStyleIndex} - onChange={selectChartStyleIndex} + onChange={(v: string) => setSelectedChartStyleIndex(parseInt(v, 10))} />
@@ -271,7 +260,7 @@ function Config({ configs }: ConfigImplProps) {
diff --git a/src/components/Root.tsx b/src/components/Root.tsx index 195fe6a92..9d05a48ca 100644 --- a/src/components/Root.tsx +++ b/src/components/Root.tsx @@ -6,6 +6,7 @@ import { useAtom } from 'jotai'; import * as React from 'react'; import { RouteObject } from 'react-router'; import { HashRouter as Router, useRoutes } from 'react-router-dom'; +import { Toaster } from 'sonner'; import { About } from 'src/components/about/About'; import Loading from 'src/components/Loading'; import { Head } from 'src/components/shared/Head'; @@ -15,8 +16,7 @@ import { AppConfigSideEffect } from '$src/components/fn/AppConfigSideEffect'; import { darkModePureBlackToggleAtom } from '$src/store/app'; import { actions, initialState } from '../store'; -import APIConfig from './APIConfig'; -import APIDiscovery from './APIDiscovery'; +import { Backend } from './backend/Backend'; import { MutableConnRefCtx } from './conns/ConnCtx'; import ErrorBoundary from './ErrorBoundary'; import Home from './Home'; @@ -24,7 +24,6 @@ import Loading2 from './Loading2'; import s0 from './Root.module.scss'; import SideBar from './SideBar'; import StateProvider from './StateProvider'; -import StyleGuide from './StyleGuide'; const { lazy, Suspense } = React; @@ -33,6 +32,7 @@ const Config = lazy(() => import('./Config')); const Logs = lazy(() => import('./Logs')); const Proxies = lazy(() => import('./proxies/Proxies')); const Rules = lazy(() => import('./Rules')); +const StyleGuide = lazy(() => import('$src/components/style/StyleGuide')) const routes = [ { path: '/', element: }, @@ -59,7 +59,6 @@ function RouteInnerApp() { function SideBarApp() { return ( <> -
}> @@ -72,7 +71,7 @@ function SideBarApp() { function App() { return useRoutes([ - { path: '/backend', element: }, + { path: '/backend', element: }, { path: '*', element: }, ]); } @@ -80,7 +79,12 @@ function App() { function AppShell({ children }: { children: React.ReactNode }) { const [pureBlackDark] = useAtom(darkModePureBlackToggleAtom); const clazz = cx(s0.app, { pureBlackDark }); - return
{children}
; + return ( + <> + +
{children}
+ + ); } const Root = () => ( diff --git a/src/components/SideBar.tsx b/src/components/SideBar.tsx index 36397e94f..ed94581f8 100644 --- a/src/components/SideBar.tsx +++ b/src/components/SideBar.tsx @@ -5,8 +5,8 @@ import { Info } from 'react-feather'; import { useTranslation } from 'react-i18next'; import { FcAreaChart, FcDocument, FcGlobe, FcLink, FcRuler, FcSettings } from 'react-icons/fc'; import { Link, useLocation } from 'react-router-dom'; -import { ThemeSwitcher } from 'src/components/shared/ThemeSwitcher'; +import { ThemeSwitcher } from './shared/ThemeSwitcher'; import s from './SideBar.module.scss'; const icons = { diff --git a/src/components/SvgYacd.tsx b/src/components/SvgYacd.tsx index 3201969da..e636de23e 100644 --- a/src/components/SvgYacd.tsx +++ b/src/components/SvgYacd.tsx @@ -9,18 +9,20 @@ type Props = { animate?: boolean; c0?: string; c1?: string; - stroke?: string; + shapeStroke?: string; eye?: string; + eyeStroke?: string; mouth?: string; }; -function SvgYacd({ +export default function SvgYacd({ width = 320, height = 320, animate = false, c0 = 'currentColor', - stroke = '#eee', + shapeStroke = '#eee', eye = '#eee', + eyeStroke = '#eee', mouth = '#eee', }: Props) { const faceClassName = cx({ [s.path]: animate }); @@ -30,14 +32,14 @@ function SvgYacd({ {/* face */} - - + + {/* mouth */} @@ -46,5 +48,3 @@ function SvgYacd({ ); } - -export default SvgYacd; diff --git a/src/components/APIConfig.module.scss b/src/components/backend/APIConfig.module.scss similarity index 90% rename from src/components/APIConfig.module.scss rename to src/components/backend/APIConfig.module.scss index 879e0e4f5..f17bcafd8 100644 --- a/src/components/APIConfig.module.scss +++ b/src/components/backend/APIConfig.module.scss @@ -11,9 +11,10 @@ align-items: center; .icon { - --stroke: #f3f3f3; + --stroke: var(--color-text-secondary); + color: #20497e; - opacity: 0.7; + opacity: 0.4; transition: opacity 400ms; &:hover { opacity: 1; diff --git a/src/components/APIConfig.tsx b/src/components/backend/APIConfig.tsx similarity index 63% rename from src/components/APIConfig.tsx rename to src/components/backend/APIConfig.tsx index c0d2b5949..ce864f503 100644 --- a/src/components/APIConfig.tsx +++ b/src/components/backend/APIConfig.tsx @@ -1,14 +1,15 @@ import { useAtom } from 'jotai'; import * as React from 'react'; import { fetchConfigs } from 'src/api/configs'; -import { BackendList } from 'src/components/BackendList'; import { clashAPIConfigsAtom, findClashAPIConfigIndex } from 'src/store/app'; import { ClashAPIConfig } from 'src/types'; +import Field from '$src/components//Field'; +import { BackendList } from '$src/components/backend/BackendList'; +import Button from '$src/components/Button'; +import SvgYacd from '$src/components/SvgYacd'; + import s0 from './APIConfig.module.scss'; -import Button from './Button'; -import Field from './Field'; -import SvgYacd from './SvgYacd'; const { useState, useRef, useCallback, useEffect } = React; const Ok = 0; @@ -23,7 +24,6 @@ export default function APIConfig() { const [errMsg, setErrMsg] = useState(''); const userTouchedFlagRef = useRef(false); - const contentEl = useRef(null); const handleInputOnChange = useCallback>((e) => { userTouchedFlagRef.current = true; @@ -62,20 +62,10 @@ export default function APIConfig() { }); }, [baseURL, secret, metaLabel, apiConfigs, setApiConfigs]); - const handleContentOnKeyDown = useCallback( - (e: React.KeyboardEvent) => { - if ( - e.target instanceof Element && - (!e.target.tagName || e.target.tagName.toUpperCase() !== 'INPUT') - ) { - return; - } - if (e.key !== 'Enter') return; - - onConfirm(); - }, - [onConfirm], - ); + const onSubmit = useCallback((e: React.FormEvent) => { + e.preventDefault(); + onConfirm(); + }, [onConfirm]) const detectApiServer = async () => { // if there is already a clash API server at `/`, just use it as default value @@ -91,49 +81,58 @@ export default function APIConfig() { }, []); return ( - // eslint-disable-next-line jsx-a11y/no-static-element-interactions -
+
- +
-
-
- - +
+
+
+ + +
+ {errMsg ?
{errMsg}
: null} +
+ +
- {errMsg ?
{errMsg}
: null} -
- +
+
-
-
-
+
diff --git a/src/components/backend/Backend.tsx b/src/components/backend/Backend.tsx new file mode 100644 index 000000000..197487cf8 --- /dev/null +++ b/src/components/backend/Backend.tsx @@ -0,0 +1,22 @@ +import React from 'react'; + +import { ThemeSwitcher } from '../shared/ThemeSwitcher'; +import APIConfig from './APIConfig'; + +export function Backend() { + return ( + <> + +
+ +
+ + ); +} diff --git a/src/components/BackendList.module.scss b/src/components/backend/BackendList.module.scss similarity index 96% rename from src/components/BackendList.module.scss rename to src/components/backend/BackendList.module.scss index df2305a8e..54c672fc4 100644 --- a/src/components/BackendList.module.scss +++ b/src/components/backend/BackendList.module.scss @@ -19,6 +19,10 @@ column-gap: 10px; border: 1px solid var(--bg-near-transparent); + &.isSelected { + border-color: #387cec; + } + .right { display: grid; column-gap: 10px; diff --git a/src/components/BackendList.tsx b/src/components/backend/BackendList.tsx similarity index 57% rename from src/components/BackendList.tsx rename to src/components/backend/BackendList.tsx index 2e0c90344..021a46ec4 100644 --- a/src/components/BackendList.tsx +++ b/src/components/backend/BackendList.tsx @@ -2,8 +2,13 @@ import cx from 'clsx'; import { useAtom } from 'jotai'; import * as React from 'react'; import { Eye, EyeOff, X as Close } from 'react-feather'; +import { useNavigate } from 'react-router'; +import { toast } from 'sonner'; +import { req } from '$src/api/fetch'; import { useToggle } from '$src/hooks/basic'; +import { getURLAndInit } from '$src/misc/request-helper'; +import { noop } from '$src/misc/utils'; import { clashAPIConfigsAtom, findClashAPIConfigIndex, @@ -13,11 +18,11 @@ import { ClashAPIConfig } from '$src/types'; import s from './BackendList.module.scss'; +const PASS_THRU_ERROR = {}; + export function BackendList() { const [apiConfigs, setApiConfigs] = useAtom(clashAPIConfigsAtom); - const [selectedClashAPIConfigIndex, setSelectedClashAPIConfigIndex] = useAtom( - selectedClashAPIConfigIndexAtom, - ); + const [currIdx, setCurrIdx] = useAtom(selectedClashAPIConfigIndexAtom); const removeClashAPIConfig = React.useCallback( (conf: ClashAPIConfig) => { const idx = findClashAPIConfigIndex(apiConfigs, conf); @@ -25,49 +30,61 @@ export function BackendList() { apiConfigs.splice(idx, 1); return [...apiConfigs]; }); - if (idx === selectedClashAPIConfigIndex) { - setSelectedClashAPIConfigIndex(0); - } else if (idx < selectedClashAPIConfigIndex) { - setSelectedClashAPIConfigIndex(selectedClashAPIConfigIndex - 1); - } - }, - [apiConfigs, selectedClashAPIConfigIndex, setApiConfigs, setSelectedClashAPIConfigIndex], - ); - - const selectClashAPIConfig = React.useCallback( - (conf: ClashAPIConfig) => { - const idx = findClashAPIConfigIndex(apiConfigs, conf); - const curr = selectedClashAPIConfigIndex; - if (curr !== idx) { - setSelectedClashAPIConfigIndex(idx); - } - - // manual clean up is too complex - // we just reload the app - try { - window.location.reload(); - } catch (err) { - // ignore + if (idx === currIdx) { + setCurrIdx(0); + } else if (idx < currIdx) { + setCurrIdx(currIdx - 1); } }, - [apiConfigs, selectedClashAPIConfigIndex, setSelectedClashAPIConfigIndex], + [apiConfigs, currIdx, setApiConfigs, setCurrIdx], ); - // const { - // app: { selectClashAPIConfig }, - // } = useStoreActions(); + const navigate = useNavigate(); - const onRemove = React.useCallback( - (conf: ClashAPIConfig) => { - removeClashAPIConfig(conf); - }, - [removeClashAPIConfig], - ); const onSelect = React.useCallback( - (conf: ClashAPIConfig) => { - selectClashAPIConfig(conf); + async (conf: ClashAPIConfig) => { + const idx = findClashAPIConfigIndex(apiConfigs, conf); + const { url, init } = getURLAndInit(apiConfigs[idx]); + await req(url, init) + .then( + (res) => res.json(), + (err) => { + console.log(err); + toast.error('Failed to connect'); + throw PASS_THRU_ERROR; + }, + ) + .then( + (data) => { + if (typeof data['hello'] !== 'string') { + console.log('Response:', data); + toast.error('Unexpected response'); + throw PASS_THRU_ERROR; + } + }, + (err) => { + if (err === PASS_THRU_ERROR) throw PASS_THRU_ERROR; + console.log(err); + toast.error('Unexpected response'); + throw PASS_THRU_ERROR; + }, + ) + .then(() => { + if (currIdx === idx) { + navigate('/', { replace: true }); + } else { + setCurrIdx(idx); + // manual clean up is too complex + // we just reload the app + try { + window.location.href = '/'; + } catch (err) { + // ignore + } + } + }, noop); }, - [selectClashAPIConfig], + [apiConfigs, currIdx, setCurrIdx, navigate], ); return ( @@ -76,13 +93,13 @@ export function BackendList() { {apiConfigs.map((item, idx) => { return (
  • @@ -113,9 +130,13 @@ function Item({ return ( <> - + {disableRemove ? ( + + ) : ( + + )}
    {conf.metaLabel ? ( diff --git a/src/components/fn/AppConfigSideEffect.tsx b/src/components/fn/AppConfigSideEffect.tsx index c8f6dc769..d4e87d4ed 100644 --- a/src/components/fn/AppConfigSideEffect.tsx +++ b/src/components/fn/AppConfigSideEffect.tsx @@ -2,7 +2,7 @@ import { useAtom } from 'jotai'; import { useEffect } from 'react'; import { saveState } from '$src/misc/storage'; -import { debounce } from '$src/misc/utils'; +import { throttle } from '$src/misc/utils'; import { autoCloseOldConnsAtom, clashAPIConfigsAtom, @@ -23,7 +23,7 @@ function save0() { if (stateRef) saveState(stateRef); } -const save = debounce(save0, 500); +const save = throttle(save0, 500); export function AppConfigSideEffect() { const [selectedClashAPIConfigIndex] = useAtom(selectedClashAPIConfigIndexAtom); diff --git a/src/components/shared/ThemeSwitcher.module.scss b/src/components/shared/ThemeSwitcher.module.scss index 1c8cf9569..e50a9eaf8 100644 --- a/src/components/shared/ThemeSwitcher.module.scss +++ b/src/components/shared/ThemeSwitcher.module.scss @@ -1,4 +1,4 @@ -[data-reach-menu-button] { +.MenubarTrigger { --sz: 40px; width: var(--sz); @@ -9,6 +9,7 @@ justify-content: center; -webkit-appearance: none; + appearance: none; outline: none; user-select: none; cursor: pointer; @@ -26,7 +27,7 @@ } } -[data-reach-menu-list] { +.MenubarContent { background: var(--bg-tooltip); color: var(--color-text); border: 1px solid #555; @@ -34,15 +35,16 @@ border-radius: 8px; } -[data-reach-menu-item] { +.MenubarItem { padding: 5px 16px 5px 6px; border-radius: 7px; cursor: pointer; display: flex; align-items: center; + outline: none; } -[data-reach-menu-item][data-selected] { +.MenubarItem[data-highlighted] { background: var(--color-focus-blue); color: #f7f7f7; } diff --git a/src/components/shared/ThemeSwitcher.tsx b/src/components/shared/ThemeSwitcher.tsx index 98e8745e1..399493595 100644 --- a/src/components/shared/ThemeSwitcher.tsx +++ b/src/components/shared/ThemeSwitcher.tsx @@ -1,4 +1,4 @@ -import { Menu, MenuButton, MenuItem, MenuList } from '@reach/menu-button'; +import * as Menubar from '@radix-ui/react-menubar'; import { Tooltip } from '@reach/tooltip'; import cx from 'clsx'; import { useAtom } from 'jotai'; @@ -12,24 +12,29 @@ import { ThemeType } from '$src/store/types'; import s from './ThemeSwitcher.module.scss'; -export function ThemeSwitcher() { - const [theme, setThemeAtom] = useAtom(themeAtom); - const { t } = useTranslation(); +const ALL = [ + { v: 'auto', l: 'Auto' }, + { v: 'dark', l: 'Dark' }, + { v: 'light', l: 'Light' }, +]; - const themeIcon = React.useMemo(() => { - switch (theme) { - case 'dark': - return ; - case 'auto': - return ; - case 'light': - return ; - default: - console.assert(false, 'Unknown theme'); - return ; - } - }, [theme]); +function ThemeIcon({ theme }: { theme: ThemeType }) { + switch (theme) { + case 'dark': + return ; + case 'auto': + return ; + case 'light': + return ; + default: + console.assert(false, 'Unknown theme'); + return ; + } +} +export function ThemeSwitcher() { + const { t } = useTranslation(); + const [theme, setThemeAtom] = useAtom(themeAtom); const onSelect = React.useCallback( (v: ThemeType) => { setThemeAtom(v); @@ -39,16 +44,28 @@ export function ThemeSwitcher() { ); return ( - - - {themeIcon} - - - - - - - + + + + + + + + + + {ALL.map((it) => ( + + ))} + + + + ); } @@ -60,12 +77,12 @@ function ThemeMenuItem(props: { }) { const cls = cx(s.checkWrapper, { [s.active]: props.active }); return ( - props.onSelect(props.value)}> + props.onSelect(props.value)}> {props.label} - + ); } diff --git a/src/components/StyleGuide.tsx b/src/components/style/StyleGuide.tsx similarity index 75% rename from src/components/StyleGuide.tsx rename to src/components/style/StyleGuide.tsx index ad5b20e30..1f2259163 100644 --- a/src/components/StyleGuide.tsx +++ b/src/components/style/StyleGuide.tsx @@ -2,11 +2,13 @@ import React, { PureComponent } from 'react'; import { Zap } from 'react-feather'; import Loading from 'src/components/Loading'; -import Button from './Button'; -import { ToggleInput } from './form/Toggle'; -import Input from './Input'; -import { ZapAnimated } from './shared/ZapAnimated'; -import ToggleSwitch from './ToggleSwitch'; +import Button from '$src/components/Button'; +import { ToggleInput } from '$src/components/form/Toggle'; +import Input from '$src/components/Input'; +import { ZapAnimated } from '$src/components/shared/ZapAnimated'; +import ToggleSwitch from '$src/components/ToggleSwitch'; + +import { ThemeSwitcher } from '../shared/ThemeSwitcher'; const noop = () => { /* empty */ @@ -26,10 +28,13 @@ const Pane = ({ children, style }: { children: React.ReactNode; style?: React.CS
    {children}
    ); -class StyleGuide extends PureComponent { +export default class StyleGuide extends PureComponent { render() { return (
    + + +