diff --git a/package.json b/package.json index f53b6dd8..0ec4a6c0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "intuition-chrome-extension", "displayName": "Intuition Chrome Extension", - "version": "0.1.46", + "version": "0.1.47", "description": "", "author": "THP-Lab.org", "scripts": { @@ -20,7 +20,9 @@ "@graphql-codegen/typescript-document-nodes": "^4.0.11", "@plasmohq/storage": "^1.15.0", "@radix-ui/react-collapsible": "^1.1.4", + "@radix-ui/react-dropdown-menu": "^2.1.15", "@radix-ui/react-hover-card": "^1.1.7", + "@radix-ui/react-icons": "^1.3.2", "@radix-ui/react-slot": "^1.1.2", "@tanstack/react-query": "^5.69.0", "@types/three": "^0.175.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2044eecb..875ce85e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,9 +32,15 @@ importers: '@radix-ui/react-collapsible': specifier: ^1.1.4 version: 1.1.4(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-dropdown-menu': + specifier: ^2.1.15 + version: 2.1.15(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@radix-ui/react-hover-card': specifier: ^1.1.7 version: 1.1.7(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-icons': + specifier: ^1.3.2 + version: 1.3.2(react@18.2.0) '@radix-ui/react-slot': specifier: ^1.1.2 version: 1.1.2(@types/react@18.2.48)(react@18.2.0) @@ -2467,6 +2473,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-arrow@1.1.7': + resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-avatar@1.1.3': resolution: {integrity: sha512-Paen00T4P8L8gd9bNsRMw7Cbaz85oxiv+hzomsRZgFm2byltPFDtfcoqlWJ8GyZlIBWgLssJlzLCnKU0G0302g==} peerDependencies: @@ -2532,6 +2551,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-collection@1.1.7': + resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-compose-refs@1.1.1': resolution: {integrity: sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==} peerDependencies: @@ -2603,6 +2635,28 @@ packages: '@types/react': optional: true + '@radix-ui/react-direction@1.1.1': + resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dismissable-layer@1.1.10': + resolution: {integrity: sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-dismissable-layer@1.1.5': resolution: {integrity: sha512-E4TywXY6UsXNRhFrECa5HAvE5/4BFcGyfTyK36gP+pAW1ed7UTK4vKwdr53gAJYwqbfCWC6ATvJa3J3R/9+Qrg==} peerDependencies: @@ -2629,8 +2683,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-dropdown-menu@2.1.6': - resolution: {integrity: sha512-no3X7V5fD487wab/ZYSHXq3H37u4NVeLDKI/Ks724X/eEFSSEFYZxWgsIlr1UBeEyDaM29HM5x9p1Nv8DuTYPA==} + '@radix-ui/react-dropdown-menu@2.1.15': + resolution: {integrity: sha512-mIBnOjgwo9AH3FyKaSWoSu/dYj6VdhJ7frEPiGTeXCdUFHjl9h3mFh2wwhEtINOmYXWhdpf1rY2minFsmaNgVQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2651,6 +2705,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-focus-guards@1.1.2': + resolution: {integrity: sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-focus-scope@1.1.2': resolution: {integrity: sha512-zxwE80FCU7lcXUGWkdt6XpTTCKPitG1XKOwViTxHVKIJhZl9MvIl2dVHeZENCWD9+EdWv05wlaEkRXUykU27RA==} peerDependencies: @@ -2664,6 +2727,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-focus-scope@1.1.7': + resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-hover-card@1.1.7': resolution: {integrity: sha512-HwM03kP8psrv21J1+9T/hhxi0f5rARVbqIZl9+IAq13l4j4fX+oGIuxisukZZmebO7J35w9gpoILvtG8bbph0w==} peerDependencies: @@ -2677,6 +2753,11 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-icons@1.3.2': + resolution: {integrity: sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==} + peerDependencies: + react: ^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc + '@radix-ui/react-id@1.1.0': resolution: {integrity: sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==} peerDependencies: @@ -2708,6 +2789,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-menu@2.1.15': + resolution: {integrity: sha512-tVlmA3Vb9n8SZSd+YSbuFR66l87Wiy4du+YE+0hzKQEANA+7cWKH1WgqcEX4pXqxUFQKrWQGHdvEfw00TjFiew==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-menu@2.1.6': resolution: {integrity: sha512-tBBb5CXDJW3t2mo9WlO7r6GTmWV0F0uzHZVFmlRmYpiSK1CDU5IKojP1pm7oknpBOrFZx/YgBRW9oorPO2S/Lg==} peerDependencies: @@ -2773,6 +2867,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-popper@1.2.7': + resolution: {integrity: sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-portal@1.1.4': resolution: {integrity: sha512-sn2O9k1rPFYVyKd5LAJfo96JlSGVFpa1fS6UuBJfrZadudiw5tAmru+n1x7aMRQ84qDM71Zh1+SzK5QwU0tJfA==} peerDependencies: @@ -2799,6 +2906,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-portal@1.1.9': + resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-presence@1.1.2': resolution: {integrity: sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==} peerDependencies: @@ -2825,6 +2945,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-presence@1.1.4': + resolution: {integrity: sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-primitive@2.0.2': resolution: {integrity: sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==} peerDependencies: @@ -2851,6 +2984,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-primitive@2.1.3': + resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-progress@1.1.2': resolution: {integrity: sha512-u1IgJFQ4zNAUTjGdDL5dcl/U8ntOR6jsnhxKb5RKp5Ozwl88xKR9EqRZOe/Mk8tnx0x5tNUe2F+MzsyjqMg0MA==} peerDependencies: @@ -2877,6 +3023,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-roving-focus@1.1.10': + resolution: {integrity: sha512-dT9aOXUen9JSsxnMPv/0VqySQf5eDQ6LCk5Sw28kamz8wSOW2bJdlX2Bg5VUIIcV+6XlHpWTIuTPCf/UNIyq8Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-roving-focus@1.1.2': resolution: {integrity: sha512-zgMQWkNO169GtGqRvYrzb0Zf8NhMHS2DuEB/TiEmVnpr5OqPU3i8lfbxaAmC2J/KYuIQxyoQQ6DxepyXp61/xw==} peerDependencies: @@ -2947,6 +3106,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-slot@1.2.3': + resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-switch@1.1.3': resolution: {integrity: sha512-1nc+vjEOQkJVsJtWPSiISGT6OKm4SiOdjMo+/icLxo2G4vxz1GntC5MzfL4v8ey9OEfw787QCD1y3mUv0NiFEQ==} peerDependencies: @@ -3022,6 +3190,24 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-controllable-state@1.2.2': + resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-effect-event@0.0.2': + resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-use-escape-keydown@1.1.0': resolution: {integrity: sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==} peerDependencies: @@ -6632,7 +6818,7 @@ snapshots: '@radix-ui/react-checkbox': 1.1.4(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@radix-ui/react-context-menu': 2.2.6(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@radix-ui/react-dialog': 1.1.6(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@radix-ui/react-dropdown-menu': 2.1.6(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-dropdown-menu': 2.1.15(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@radix-ui/react-hover-card': 1.1.7(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@radix-ui/react-label': 2.1.2(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@radix-ui/react-navigation-menu': 1.2.5(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -9501,6 +9687,15 @@ snapshots: '@types/react': 18.2.48 '@types/react-dom': 18.2.18 + '@radix-ui/react-arrow@1.1.7(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.2.48 + '@types/react-dom': 18.2.18 + '@radix-ui/react-avatar@1.1.3(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@radix-ui/react-context': 1.1.1(@types/react@18.2.48)(react@18.2.0) @@ -9573,6 +9768,18 @@ snapshots: '@types/react': 18.2.48 '@types/react-dom': 18.2.18 + '@radix-ui/react-collection@1.1.7(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@18.2.48)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.2.48 + '@types/react-dom': 18.2.18 + '@radix-ui/react-compose-refs@1.1.1(@types/react@18.2.48)(react@18.2.0)': dependencies: react: 18.2.0 @@ -9639,6 +9846,25 @@ snapshots: optionalDependencies: '@types/react': 18.2.48 + '@radix-ui/react-direction@1.1.1(@types/react@18.2.48)(react@18.2.0)': + dependencies: + react: 18.2.0 + optionalDependencies: + '@types/react': 18.2.48 + + '@radix-ui/react-dismissable-layer@1.1.10(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@18.2.48)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.2.48 + '@types/react-dom': 18.2.18 + '@radix-ui/react-dismissable-layer@1.1.5(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@radix-ui/primitive': 1.1.1 @@ -9665,15 +9891,15 @@ snapshots: '@types/react': 18.2.48 '@types/react-dom': 18.2.18 - '@radix-ui/react-dropdown-menu@2.1.6(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@radix-ui/react-dropdown-menu@2.1.15(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: - '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.2.48)(react@18.2.0) - '@radix-ui/react-context': 1.1.1(@types/react@18.2.48)(react@18.2.0) - '@radix-ui/react-id': 1.1.0(@types/react@18.2.48)(react@18.2.0) - '@radix-ui/react-menu': 2.1.6(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-menu': 2.1.15(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.2.48)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) optionalDependencies: @@ -9686,6 +9912,12 @@ snapshots: optionalDependencies: '@types/react': 18.2.48 + '@radix-ui/react-focus-guards@1.1.2(@types/react@18.2.48)(react@18.2.0)': + dependencies: + react: 18.2.0 + optionalDependencies: + '@types/react': 18.2.48 + '@radix-ui/react-focus-scope@1.1.2(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.2.48)(react@18.2.0) @@ -9697,6 +9929,17 @@ snapshots: '@types/react': 18.2.48 '@types/react-dom': 18.2.18 + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.2.48)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.2.48 + '@types/react-dom': 18.2.18 + '@radix-ui/react-hover-card@1.1.7(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@radix-ui/primitive': 1.1.2 @@ -9714,6 +9957,10 @@ snapshots: '@types/react': 18.2.48 '@types/react-dom': 18.2.18 + '@radix-ui/react-icons@1.3.2(react@18.2.0)': + dependencies: + react: 18.2.0 + '@radix-ui/react-id@1.1.0(@types/react@18.2.48)(react@18.2.0)': dependencies: '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.2.48)(react@18.2.0) @@ -9737,6 +9984,32 @@ snapshots: '@types/react': 18.2.48 '@types/react-dom': 18.2.18 + '@radix-ui/react-menu@2.1.15(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-focus-guards': 1.1.2(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-popper': 1.2.7(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-roving-focus': 1.1.10(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.2.48)(react@18.2.0) + aria-hidden: 1.2.4 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-remove-scroll: 2.6.3(@types/react@18.2.48)(react@18.2.0) + optionalDependencies: + '@types/react': 18.2.48 + '@types/react-dom': 18.2.18 + '@radix-ui/react-menu@2.1.6(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@radix-ui/primitive': 1.1.1 @@ -9844,6 +10117,24 @@ snapshots: '@types/react': 18.2.48 '@types/react-dom': 18.2.18 + '@radix-ui/react-popper@1.2.7(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@floating-ui/react-dom': 2.1.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-use-rect': 1.1.1(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/rect': 1.1.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.2.48 + '@types/react-dom': 18.2.18 + '@radix-ui/react-portal@1.1.4(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -9864,6 +10155,16 @@ snapshots: '@types/react': 18.2.48 '@types/react-dom': 18.2.18 + '@radix-ui/react-portal@1.1.9(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.2.48)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.2.48 + '@types/react-dom': 18.2.18 + '@radix-ui/react-presence@1.1.2(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.2.48)(react@18.2.0) @@ -9884,6 +10185,16 @@ snapshots: '@types/react': 18.2.48 '@types/react-dom': 18.2.18 + '@radix-ui/react-presence@1.1.4(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.2.48)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.2.48 + '@types/react-dom': 18.2.18 + '@radix-ui/react-primitive@2.0.2(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@radix-ui/react-slot': 1.1.2(@types/react@18.2.48)(react@18.2.0) @@ -9902,6 +10213,15 @@ snapshots: '@types/react': 18.2.48 '@types/react-dom': 18.2.18 + '@radix-ui/react-primitive@2.1.3(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/react-slot': 1.2.3(@types/react@18.2.48)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.2.48 + '@types/react-dom': 18.2.18 + '@radix-ui/react-progress@1.1.2(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@radix-ui/react-context': 1.1.1(@types/react@18.2.48)(react@18.2.0) @@ -9930,6 +10250,23 @@ snapshots: '@types/react': 18.2.48 '@types/react-dom': 18.2.18 + '@radix-ui/react-roving-focus@1.1.10(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.2.48)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.2.48 + '@types/react-dom': 18.2.18 + '@radix-ui/react-roving-focus@1.1.2(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@radix-ui/primitive': 1.1.1 @@ -10016,6 +10353,13 @@ snapshots: optionalDependencies: '@types/react': 18.2.48 + '@radix-ui/react-slot@1.2.3(@types/react@18.2.48)(react@18.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.2.48)(react@18.2.0) + react: 18.2.0 + optionalDependencies: + '@types/react': 18.2.48 + '@radix-ui/react-switch@1.1.3(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@radix-ui/primitive': 1.1.1 @@ -10093,6 +10437,21 @@ snapshots: optionalDependencies: '@types/react': 18.2.48 + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@18.2.48)(react@18.2.0)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@18.2.48)(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.2.48)(react@18.2.0) + react: 18.2.0 + optionalDependencies: + '@types/react': 18.2.48 + + '@radix-ui/react-use-effect-event@0.0.2(@types/react@18.2.48)(react@18.2.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.2.48)(react@18.2.0) + react: 18.2.0 + optionalDependencies: + '@types/react': 18.2.48 + '@radix-ui/react-use-escape-keydown@1.1.0(@types/react@18.2.48)(react@18.2.0)': dependencies: '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.2.48)(react@18.2.0) diff --git a/src/background.ts b/src/background.ts index fa23ef55..fad5b8ea 100644 --- a/src/background.ts +++ b/src/background.ts @@ -1,5 +1,23 @@ export {} +let pendingSidepanelRoute: string | null = null; +let sidepanelPort: chrome.runtime.Port | null = null; + +chrome.runtime.onConnect.addListener((port) => { + if (port.name === "sidepanel-nav") { + sidepanelPort = port; + port.onMessage.addListener((msg) => { + if (msg === "SIDEPANEL_READY" && pendingSidepanelRoute) { + port.postMessage({ action: "NAVIGATE_SIDEPANEL", route: pendingSidepanelRoute }); + pendingSidepanelRoute = null; + } + }); + port.onDisconnect.addListener(() => { + sidepanelPort = null; + }); + } +}); + chrome.runtime.onMessage.addListener((message, sender) => { if (message.type === "open_sidepanel") { const tabId = sender.tab?.id @@ -7,6 +25,18 @@ chrome.runtime.onMessage.addListener((message, sender) => { if (!tabId || !windowId) return - chrome.sidePanel.open({ tabId, windowId }) + pendingSidepanelRoute = message.route || null; + + chrome.sidePanel.open({ tabId, windowId }); } -}) +}); + +chrome.tabs.onUpdated.addListener((tabId, info) => { + if (info.status === "complete") { + chrome.tabs.sendMessage(tabId, { action: "REFRESH_CLAIMS" }); + } +}); + +chrome.tabs.onActivated.addListener(({ tabId }) => { + chrome.tabs.sendMessage(tabId, { action: "REFRESH_CLAIMS" }); +}); \ No newline at end of file diff --git a/src/components/ReportDropdown.tsx b/src/components/ReportDropdown.tsx new file mode 100644 index 00000000..def8b4aa --- /dev/null +++ b/src/components/ReportDropdown.tsx @@ -0,0 +1,63 @@ +import * as DropdownMenu from "@radix-ui/react-dropdown-menu"; +import React from "react"; +import { useSignalProcess } from "../hooks/useSignalProcess" + +const ChevronDown = () => ( + + + +); + +const SignalDropdown = ({ atoms, uri }) => { + const { handleSignal } = useSignalProcess({ + atoms, + uri, + onSuccess: () => {/* popup success */}, + onError: () => {/* popup error */} + }) + + const shadowRoot = document.getElementById("plasmo-inline-example-unique-id")?.shadowRoot + + return ( + + + + + + e.stopPropagation()} + onMouseDown={e => e.stopPropagation()} + > + handleSignal("scam")}> + + + handleSignal("trustworthy")}> + + + + + + ); +}; + +export default SignalDropdown; \ No newline at end of file diff --git a/src/components/WarningPopup.tsx b/src/components/WarningPopup.tsx new file mode 100644 index 00000000..425aa0ab --- /dev/null +++ b/src/components/WarningPopup.tsx @@ -0,0 +1,125 @@ +import React, { useEffect, useState } from "react" +import { cn } from "~/src/lib/utils" +import VoteButtons from "~src/components/VoteButtons" + +interface WarningPopupProps { + message: string + offset: number + bgColor?: string + targetClaim?: any + forceVisible?: boolean +} + +/** + * A small popup to display status warnings or confirmations. + * Appears below its parent icon at a given offset. + */ +const WarningPopup: React.FC = ({ + message, + offset, + bgColor = "red", + targetClaim, + forceVisible = false +}) => { + const [visible, setVisible] = useState(forceVisible) + const [shouldRender, setShouldRender] = useState(forceVisible) + + const ANIMATION_DURATION = 500 + + useEffect(() => { + if (forceVisible) { + setShouldRender(true) + setVisible(true) + } else { + setVisible(false) + const tm = setTimeout(() => setShouldRender(false), ANIMATION_DURATION) + return () => clearTimeout(tm) + } + }, [forceVisible]) + + const textColor = bgColor === "red" ? "#b50606" : "#228e01" + const borderColor = bgColor === "red" ? "#b50606" : "#228e01" + + const vaultId = targetClaim?.term_id + ? BigInt(targetClaim.term_id) + : undefined + const counterVaultId = targetClaim?.counter_term_id + ? BigInt(targetClaim.counter_term_id) + : undefined + const numPositionsFor = targetClaim?.term?.positions_aggregate?.aggregate?.count + const numPositionsAgainst = targetClaim?.counter_term?.positions_aggregate?.aggregate?.count + + const userStake = Number(targetClaim?.positions?.[0]?.shares ?? 0) + const userCounterStake = Number(targetClaim?.counter_positions?.[0]?.shares ?? 0) + + const initialVote: VoteChoice | undefined = + userStake > 0 + ? "for" + : userCounterStake > 0 + ? "against" + : undefined + + if (!shouldRender) return null + + return ( +
e.stopPropagation()} + onClick={e => e.stopPropagation()} + style={{ + position: "absolute", + top: offset, + left: "50%", + transform: ` + translateX(-80%) + translateY(${visible ? 0 : '-5px'}) + `, + background: "#0f0f0f", + color: textColor, + padding: "4px", + borderRadius: "8px", + fontSize: "0.875rem", + fontWeight: "500", + whiteSpace: "nowrap", + pointerEvents: "auto", + zIndex: 9998, + border: `1px solid ${borderColor}`, + boxShadow: `0 4px 12px rgba(0, 0, 0, 0.3), 0 0 0 1px ${borderColor}20`, + textAlign: "center", + fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif", + letterSpacing: "0.025em", + backdropFilter: "blur(8px)", + width: "180px", + minWidth: undefined, + opacity: visible ? 1 : 0, + transition: ` + opacity ${ANIMATION_DURATION}ms cubic-bezier(0.4, 0, 0.2, 1), + transform ${ANIMATION_DURATION}ms cubic-bezier(0.34, 1.56, 0.64, 1) + `, + willChange: "transform, opacity" + }} + > +
+ {message} +
+ +
+ +
+
+ ) +} + +export default WarningPopup diff --git a/src/components/content.tsx b/src/components/content.tsx index d1c3eca1..a119a7ac 100644 --- a/src/components/content.tsx +++ b/src/components/content.tsx @@ -4,9 +4,9 @@ import { apolloClient } from '~src/lib/apolo-client' import React, { type ReactNode, useEffect } from "react" import { Route, - BrowserRouter as Router, Routes, - Navigate + Navigate, + useNavigate } from "react-router-dom" import { configureClient } from "@warzieram/graphql" @@ -39,9 +39,7 @@ import ProfileLayout from "./profile/ProfileLayout" import "../styles/global.css" import umamiScriptUrl from "url:../../assets/umami.js" -import TagsPage from "~src/pages/TagsPage" - -const API_URL = "https://prod.base-sepolia.intuition.sh/v1/graphql" +const API_URL = "https://prod.base.intuition-api.com/v1/graphql" configureClient({ apiUrl: API_URL }) const queryClient = new QueryClient() @@ -65,22 +63,32 @@ const Content = ({ children }: ContentProps) => { }, []) const { navType } = useNavigation() + const navigate = useNavigate(); + + useEffect(() => { + const port = chrome.runtime.connect({ name: "sidepanel-nav" }); + port.postMessage("SIDEPANEL_READY"); + port.onMessage.addListener((msg) => { + if (msg.action === "NAVIGATE_SIDEPANEL" && msg.route) { + console.log("[SIDEPANEL] NAVIGATE_SIDEPANEL received via port, navigating to:", msg.route); + navigate(msg.route); + } + }); + return () => port.disconnect(); + }, [navigate]); - console.log(queryClient); - return ( - - - {navType === "classic" && } -
- {children} -
- - } /> - } /> + + {navType === "classic" && } +
+ {children} +
+ + } /> + } /> }> } /> @@ -90,34 +98,33 @@ const Content = ({ children }: ContentProps) => { } /> - - } /> - } /> - - - } /> - } /> + + } /> + } /> - - } /> - } /> - } /> - } /> - } /> - } /> - } /> - -
-
- {navType === "classic" ? ( - - ) : ( - <> - - - )} - + } /> + } /> + + + + } /> + } /> + } /> + } /> + } /> + } /> + } /> +
+
+
+ {navType === "classic" ? ( + + ) : ( + <> + + + )}
diff --git a/src/components/icons/IntuitionButtonIcon.tsx b/src/components/icons/IntuitionButtonIcon.tsx new file mode 100644 index 00000000..54db70ee --- /dev/null +++ b/src/components/icons/IntuitionButtonIcon.tsx @@ -0,0 +1,92 @@ +import React from "react" +import { useTheme } from "~/src/components/ThemeProvider" +import { cn } from "~/src/lib/utils" + +interface IntuitionButtonIconProps { + className?: string + size?: number + position?: { x?: number | string; y?: number | string } + loading?: boolean + highlightColor?: string + onClick?: () => void +} + +const IntuitionButtonIcon: React.FC = ({ + className, + size = 50, + position = { x: "0px", y: "0px" }, + loading = false, + highlightColor, + onClick +}) => { + const { theme } = useTheme() + const defaultStroke = theme === "dark" ? "white" : "black" + const strokeColor = highlightColor ?? defaultStroke + const cursor = loading ? "wait" : "pointer" + + return ( +
+ + + + + +
+ ) +} + +export default IntuitionButtonIcon diff --git a/src/contents/plasmo-inline.tsx b/src/contents/plasmo-inline.tsx index 63b466c2..7fa6f6fe 100644 --- a/src/contents/plasmo-inline.tsx +++ b/src/contents/plasmo-inline.tsx @@ -1,31 +1,143 @@ -import type { PlasmoCSConfig, PlasmoGetInlineAnchor } from "plasmo" -import { useEffect, useRef, useState } from "react" -import IntuitionSearchIcon from "~src/components/icons/IntuitionSearchBar" +import { ApolloProvider } from "@apollo/client" +import { QueryClient, QueryClientProvider } from "@tanstack/react-query" +import { apolloClient } from "../lib/apolo-client" +import type { PlasmoGetStyle, PlasmoCSConfig, PlasmoGetInlineAnchor } from "plasmo" +import React, { useEffect, useRef, useState } from "react" +import IntuitionButtonIcon from "~src/components/icons/IntuitionButtonIcon" +import { useStorage } from "@plasmohq/storage/dist/hook" +import { useGetTriplesByUriQuery } from "@warzieram/graphql" +import { normalizeUrl, buildUriRegex } from "../lib/url" +import WarningPopup from "~/src/components/WarningPopup" +import ReportDropdown from "~src/components/ReportDropdown" +import styleText from "data-text:../styles/global.css" +import IntuitionIconPlus from "~src/components/icons/intuition_icon_plus" + +const queryClient = new QueryClient() export const config: PlasmoCSConfig = { matches: ["https://*/*"] } -export const getInlineAnchor: PlasmoGetInlineAnchor = () => - document.querySelector("body") +export const getInlineAnchor: PlasmoGetInlineAnchor = () => { + const body = document.querySelector("body") + if (!body) { + throw new Error("Body element not found") + } + return body +} export const getShadowHostId = () => "plasmo-inline-example-unique-id" +export const getStyle: PlasmoGetStyle = () => { + const style = document.createElement("style") + style.textContent = styleText + return style +} + function PlasmoInline() { + const iconSize = 35 const [positionY, setPositionY] = useState(50) + const [isHolding, setIsHolding] = useState(false) const draggingRef = useRef(false) + const [hovered, setHovered] = useState(false) + const [autoVisible, setAutoVisible] = useState(false) + const [showDropdown, setShowDropdown] = useState(false) + const hoverTimeout = useRef(null) + + const [walletAddress] = useStorage("metamask-account", "") + const uri = normalizeUrl(window.location.href) + const uriRegex = buildUriRegex(uri) + + const { data, loading, refetch } = useGetTriplesByUriQuery({ variables: { + uriRegex, + address: walletAddress + }}) + + const inject = () => { + if (!loading && data) { + chrome.storage.local.set( + { + claimByUriResult: { uri, data } + }, + () => { + console.log("✅ Data injected from GraphQL", data); + } + ); + } + }; + + useEffect(() => { + setAutoVisible(true) + const timer = setTimeout(() => setAutoVisible(false), 4000) + return () => clearTimeout(timer) + }, []) + + const handleIntuitionMouseEnter = () => { + hoverTimeout.current = setTimeout(() => { + setShowDropdown(true) + }, 500) + } + const handleIntuitionMouseLeave = () => { + if (hoverTimeout.current) { + clearTimeout(hoverTimeout.current) + hoverTimeout.current = null + } + } + + useEffect(() => { + const listener = (msg: any) => { + if (msg.action === "REFRESH_CLAIMS") { + console.log("[PlasmoInline] → REFRESH_CLAIMS reçu"); + refetch() + .then(() => { + inject(); + }) + .catch((e) => + console.error("[PlasmoInline] refetch() error:", e) + ); + } + }; + + chrome.runtime.onMessage.addListener(listener); + return () => { + chrome.runtime.onMessage.removeListener(listener); + }; + }, [refetch, loading, data, uri]); + + useEffect(inject, [loading, data, uri]); + + const atoms = data?.atoms ?? [] + const allClaims = atoms.flatMap(atom => [ + ...(atom.as_object_triples_aggregate?.nodes ?? []), + ...(atom.as_subject_triples_aggregate?.nodes ?? []) + ]) + const IS_ID = 877 + const SCAM_ID = 1775 + const TRUSTWORTHY_ID = 14 + const hasScam = allClaims.some( + c => c.predicate?.term_id == IS_ID && c.object?.term_id == SCAM_ID + ) + const hasTrustworthy = allClaims.some( + c => c.predicate?.term_id == IS_ID && c.object?.term_id == TRUSTWORTHY_ID + ) + const highlightColor = hasScam ? "red" : hasTrustworthy ? "green" : undefined + const showPopup = Boolean(highlightColor) && (hovered || autoVisible) + + const isWarningPopupActive = (highlightColor === "red" || highlightColor === "green") && (hovered || autoVisible) const handleMouseDown = (e: React.MouseEvent) => { + e.preventDefault() + setIsHolding(true) + draggingRef.current = false + const startY = e.clientY const startPositionY = positionY - draggingRef.current = false const handleMouseMove = (moveEvent: MouseEvent) => { const deltaY = moveEvent.clientY - startY if (Math.abs(deltaY) > 5) { draggingRef.current = true } - if (draggingRef.current) { const newY = startPositionY + (deltaY / window.innerHeight) * 100 setPositionY(Math.min(90, Math.max(0, newY))) @@ -36,8 +148,10 @@ function PlasmoInline() { window.removeEventListener("mousemove", handleMouseMove) window.removeEventListener("mouseup", handleMouseUp) + setIsHolding(false) + if (!draggingRef.current) { - handleSidePanel() + chrome.runtime.sendMessage({ type: "open_sidepanel" }) } } @@ -45,9 +159,12 @@ function PlasmoInline() { window.addEventListener("mouseup", handleMouseUp) } - const handleSidePanel = () => { - chrome.runtime.sendMessage({ type: "open_sidepanel" }) - } + const targetClaim = allClaims.find( + c => c.predicate?.term_id == IS_ID && + (c.object?.term_id == SCAM_ID || c.object?.term_id == TRUSTWORTHY_ID) + ) + + console.log("DATA CLAIM SCAM OR TRUST", targetClaim) return (
@@ -62,27 +179,90 @@ function PlasmoInline() { background: "black", color: "white", border: "1px solid #fff", - cursor: "grab", - zIndex: 9999, + cursor: isHolding ? "grabbing" : "grab", + zIndex: 1, opacity: 0.2, transition: "opacity 0.3s ease" }} - onMouseEnter={(e) => { + onMouseEnter={e => { + setHovered(true) e.currentTarget.style.opacity = "1" }} - onMouseLeave={(e) => { + onMouseLeave={e => { + setHovered(false) e.currentTarget.style.opacity = "0.2" + setShowDropdown(false) }} > - {}} - size={35} - position={{ x: 0, y: 0 }} - className="hover:opacity-80 transition-opacity" - /> +
+ {showDropdown && !isWarningPopupActive && ( + + )} + +
+ {(!data || atoms.length === 0) && !loading && ( +
{ + e.stopPropagation(); + console.log("[INLINE] Click on IntuitionIconPlus: sending open_sidepanel with /page-form"); + chrome.runtime.sendMessage({ type: "open_sidepanel", route: "/page-form" }, (response) => { + console.log("[INLINE] open_sidepanel message sent, response:", response); + }); + }} + onMouseDown={e => e.stopPropagation()} + title="Add your Intuition" + > + +
+ )} + {}} + size={iconSize} + loading={loading} + highlightColor={highlightColor} + position={{ x: 0, y: 0 }} + className="hover:opacity-80 transition-opacity" + /> +
+
+ {(highlightColor === "red" || highlightColor === "green") && ( + + )}
) } -export default PlasmoInline + +const PlasmoInlineWrapper = () => ( + + + + + +) + +export default PlasmoInlineWrapper diff --git a/src/hooks/useCreateSingleTriple.ts b/src/hooks/useCreateSingleTriple.ts new file mode 100644 index 00000000..0ea9ee11 --- /dev/null +++ b/src/hooks/useCreateSingleTriple.ts @@ -0,0 +1,43 @@ +// src/hooks/useCreateSingleTriple.ts +import { useCallback, useState } from "react" +import { Multivault } from "@0xintuition/protocol" +import { getClients } from "../lib/viemClient" + +export function useCreateSingleTriple() { + const [isLoading, setIsLoading] = useState(false) + const [error, setError] = useState(null) + const [txHash, setTxHash] = useState(null) + const [vaultId, setVaultId] = useState(null) + + const createSingleTriple = useCallback( + async (tripleInput: [bigint, bigint, bigint]) => { + setIsLoading(true) + setError(null) + setTxHash(null) + setVaultId(null) + try { + const { walletClient, publicClient } = await getClients() + const multivault = new Multivault({ walletClient, publicClient }) + const cost = await multivault.getTripleCost() + const { vaultId, hash } = await multivault.createTriple({ + subjectId: tripleInput[0], + predicateId: tripleInput[1], + objectId: tripleInput[2], + initialDeposit: cost, + wait: true + }) + setVaultId(vaultId) + setTxHash(hash) + return { vaultId, hash } + } catch (e: any) { + setError(e.message || "Erreur inconnue") + throw e + } finally { + setIsLoading(false) + } + }, + [] + ) + + return { createSingleTriple, isLoading, error, txHash, vaultId } +} \ No newline at end of file diff --git a/src/hooks/useCreateTriples.ts b/src/hooks/useCreateTriples.ts index 6ad551bb..79453773 100644 --- a/src/hooks/useCreateTriples.ts +++ b/src/hooks/useCreateTriples.ts @@ -79,11 +79,20 @@ export const useCreateTriples = () => { } }, [triples]) + const createSingleTriple = async (triple: [bigint, bigint, bigint]) => { + addTriple(triple) + await new Promise(resolve => setTimeout(resolve, 0)) + const result = await createTriples() + clearTriples() // <-- Ajoute cette ligne pour vider le state après + return result + } + return { addTriple, removeTriple, clearTriples, createTriples, + createSingleTriple, triples, isLoading, error, diff --git a/src/hooks/usePageMetadataContentScript.tsx b/src/hooks/usePageMetadataContentScript.tsx new file mode 100644 index 00000000..6154eae8 --- /dev/null +++ b/src/hooks/usePageMetadataContentScript.tsx @@ -0,0 +1,24 @@ +import { useEffect, useState } from "react" + +export function usePageMetadataContentScript() { + const [meta, setMeta] = useState({ + title: "", + description: "", + favicon: "", + url: "" + }) + + useEffect(() => { + const getMeta = (name: string) => + document.querySelector(`meta[name="${name}"]`)?.getAttribute("content") || "" + + setMeta({ + title: document.title, + description: getMeta("description"), + favicon: [...document.querySelectorAll("link[rel~='icon']")].map(el => el.href)[0] || "", + url: window.location.href + }) + }, []) + + return meta +} diff --git a/src/hooks/useSignalProcess.tsx b/src/hooks/useSignalProcess.tsx new file mode 100644 index 00000000..6fdc778b --- /dev/null +++ b/src/hooks/useSignalProcess.tsx @@ -0,0 +1,95 @@ +import { useCreateSingleTriple } from "./useCreateSingleTriple" // ton hook +import { useCreatePosition } from "./useCreatePosition" // ton hook +import { usePinThingMutation } from "@0xintuition/graphql" // pour créer un atom +import { usePageMetadataContentScript as usePageMetadata } from "./usePageMetadataContentScript" +import { Multivault } from "@0xintuition/protocol" +import { getClients } from "../lib/viemClient" + + +export function useSignalProcess({ atoms, uri, onSuccess, onError }) { + const { createSingleTriple } = useCreateSingleTriple() + const { createPosition } = useCreatePosition() + const { mutateAsync: pinThing } = usePinThingMutation() + const pageMeta = usePageMetadata() + + const getAtomWithMostVotes = (atoms) => { + if (!atoms.length) { + console.log("[SignalProcess] No existing atom found for this URL.") + return null + } + const best = atoms.reduce((mostVotedAtom, currentAtom) => { + const currentVotes = ( + currentAtom.as_object_claims_aggregate?.nodes.length + + currentAtom.as_subject_claims_aggregate?.nodes.length + ) || 0 + const mostVotes = ( + mostVotedAtom.as_object_claims_aggregate?.nodes.length + + mostVotedAtom.as_subject_claims_aggregate?.nodes.length + ) || 0 + return currentVotes > mostVotes ? currentAtom : mostVotedAtom + }) + console.log("[SignalProcess] Atom with the most votes selected:", best) + return best + } + + const getOrCreateAtom = async () => { + let atom = getAtomWithMostVotes(atoms) + if (!atom) { + console.log("[SignalProcess] Creating a new atom with metadata:", pageMeta) + try { + const result = await pinThing({ + name: pageMeta.title || "Untitled", + description: pageMeta.description || "", + image: pageMeta.favicon || "", + url: pageMeta.url || uri + }) + console.log("[SignalProcess] pinThing mutation result:", result) + const ipfsUri = result?.pinThing?.uri + if (!ipfsUri) { + throw new Error("pinThing mutation did not return an IPFS uri.") + } + + const { walletClient, publicClient } = await getClients() + const multivault = new Multivault({ walletClient, publicClient }) + const deposit = await multivault.getAtomCost() + const { vaultId, hash } = await multivault.createAtom({ + uri: ipfsUri, + initialDeposit: deposit, + wait: true + }) + atom = { id: vaultId.toString() } + console.log("[SignalProcess] New atom created on the blockchain:", atom) + } catch (err) { + console.error("[SignalProcess] Error while creating atom:", err) + throw err + } + } + return atom + } + + const handleSignal = async (type: "scam" | "trustworthy") => { + try { + console.log("[SignalProcess] Starting signal process:", type) + let atom = await getOrCreateAtom() + console.log("[SignalProcess] Atom used for triple:", atom) + + const tripleInput: [bigint, bigint, bigint] = [ + BigInt(atom.term_id), + 877n, + type === "scam" ? 1775n : 14n + ] + console.log("[SignalProcess] tripleInput:", tripleInput) + + const { vaultId } = await createSingleTriple(tripleInput) + await createPosition({ vaultId }) + console.log("[SignalProcess] Position created on vault:", vaultId) + + onSuccess?.() + } catch (e) { + console.error("[SignalProcess] Error in handleSignal:", e) + onError?.(e) + } + } + + return { handleSignal } +} \ No newline at end of file diff --git a/src/lib/url.ts b/src/lib/url.ts new file mode 100644 index 00000000..f08b2189 --- /dev/null +++ b/src/lib/url.ts @@ -0,0 +1,22 @@ +export function normalizeUrl(input: string): string { + try { + const u = new URL(input) + let hostname = u.hostname.toLowerCase() + if (hostname.startsWith("www.")) hostname = hostname.slice(4) + let pathname = u.pathname + if (pathname.endsWith("/") && pathname.length > 1) { + pathname = pathname.slice(0, -1) + } + return `https://${hostname}${pathname}${u.search}${u.hash}` + } catch { + return input + } +} + +export function buildUriRegex(rawUrl: string): string { + const canonical = normalizeUrl(rawUrl) + let withoutProto = canonical.replace(/^https?:\/\//, "") + if (withoutProto.endsWith("/")) withoutProto = withoutProto.slice(0, -1) + const escaped = withoutProto.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + return `^https?:\\/\\/(?:www\\.)?${escaped}\\/?$` +} \ No newline at end of file diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 5bddbfd7..262ebadf 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -5,139 +5,125 @@ import { Link } from "react-router-dom" import { useTheme } from "~/src/components/ThemeProvider" import { useStorage } from "@plasmohq/storage/dist/hook" import TabSystem from "~/src/components/TabSystem" -import ClaimRowLite from "~src/components/ui/ClaimRowLite"; -import AtomCard from "~src/components/AtomCard"; import EyeComponent from "~/src/components/3D/EyeComponent" import AtomCard from "~src/components/AtomCard" import ClaimRowLite from "~src/components/ui/ClaimRowLite" +import { normalizeUrl } from "../lib/url" -import TabSystem from "../components/TabSystem" -function normalizeUrl(input: string): string { - try { - const u = new URL(input) - let hostname = u.hostname.toLowerCase() - if (hostname.startsWith("www.")) hostname = hostname.slice(4) - let pathname = u.pathname - if (pathname.endsWith("/") && pathname.length > 1) { - pathname = pathname.slice(0, -1) - } - return `https://${hostname}${pathname}${u.search}${u.hash}` - } catch { - return input +function Home() { + const [currentUrl, setCurrentUrl] = useState("") + const [walletAddress] = useStorage("metamask-account", "") + const [activeTab, setActiveTab] = useState("Claims") + const [claims, setClaims] = useState([]) + const [atomsWithTags, setAtomsWithTags] = useState([]) + const [isLoading, setIsLoading] = useState(true) + +const refreshActiveTab = async () => { + const [tab] = await chrome.tabs.query({ active: true, lastFocusedWindow: true }) + const url = tab?.url ? normalizeUrl(tab.url) : "" + setCurrentUrl(url) + if (tab?.id !== undefined) { + chrome.tabs.sendMessage(tab.id, { type: "REFRESH_CLAIMS" }) } } -function buildUriRegex(rawUrl: string): string { - const canonical = normalizeUrl(rawUrl) - let withoutProto = canonical.replace(/^https?:\/\//, "") - - if (withoutProto.endsWith("/")) { - withoutProto = withoutProto.slice(0, -1) +useEffect(() => { + refreshActiveTab() + chrome.tabs.onActivated.addListener(refreshActiveTab) + chrome.tabs.onUpdated.addListener(refreshActiveTab) + return () => { + chrome.tabs.onActivated.removeListener(refreshActiveTab) + chrome.tabs.onUpdated.removeListener(refreshActiveTab) } - const escaped = withoutProto.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") - - return `^https?:\\/\\/(?:www\\.)?${escaped}\\/?$` -} +}, []) -function Home() { - const [currentUrl, setCurrentUrl] = useState("") - const [walletAddress] = useStorage("metamask-account", "") - const [activeTab, setActiveTab] = useState("Claims") - const [startRequest, setStartRequest] = useState(false) +useEffect(() => { + setIsLoading(true) + setClaims([]) + setAtomsWithTags([]) - const queryClient = useQueryClient() - console.log(queryClient) + const KEY = "claimByUriResult" - const getCurrentUrl = async () => { - const [tab] = await chrome.tabs.query({ - active: true, - lastFocusedWindow: true - }) - console.log(tab.url) - return tab.url - } + const process = (uri: string, data: any) => { + if (normalizeUrl(uri) !== currentUrl) { + setClaims([]) + setAtomsWithTags([]) + setIsLoading(false) + return + } - const refreshUrl = () => { - getCurrentUrl().then((url) => { - if (url) { - setCurrentUrl(normalizeUrl(url)) - } else { - setCurrentUrl("") - } - }) - } + const atoms = data.atoms ?? [] + + const extractedClaims = Array.from( + new Map( + atoms + .flatMap((atom: any) => [ + ...(atom.as_object_triples_aggregate?.nodes ?? []), + ...(atom.as_subject_triples_aggregate?.nodes ?? []), + ]) + .map((c: any) => [c.term_id, c]) + ).values() + ) - useEffect(() => { - getCurrentUrl().then((url) => { - if (url) setCurrentUrl(normalizeUrl(url)) - }) - chrome.tabs.onUpdated.addListener(refreshUrl) - chrome.tabs.onActivated.addListener(refreshUrl) - return () => { - chrome.tabs.onUpdated.removeListener(refreshUrl) - chrome.tabs.onActivated.removeListener(refreshUrl) - } - }, []) - - const uriRegex = buildUriRegex(currentUrl) - console.log("normalized URL:", currentUrl) - console.log("uriRegex:", uriRegex) - - const { data, loading, error } = useGetTriplesByUriQuery({variables: {uriRegex: uriRegex, address: walletAddress }}) - const atoms = data?.atoms ?? [] - console.log("current wallet address:", walletAddress) - console.log("Data :", data) - - const claims = Array.from( - new Map( - atoms - ?.flatMap((atom) => [ - ...atom.as_object_triples_aggregate.nodes, - ...atom.as_subject_triples_aggregate.nodes - ]) - .map((claim) => [claim.term_id, claim]) - ).values() - ) + const withTags = atoms.map((atom: any) => { + const tags = atom.as_subject_claims_aggregate?.nodes + ?.filter((c: any) => c.predicate?.label === "has tag") + .map((c: any) => c.object) + .filter(Boolean) ?? [] - console.log("Claims :", claims) + const unique = Array.from( + new Map(tags.map((t: any) => [t.id, t])).values() + ) - const atomsWithTags = atoms.map((atom) => { - const tags = atom.as_subject_triples_aggregate.nodes - .filter((claim) => claim.predicate.label === "has tag") - .map((claim) => claim.object) - .filter(Boolean) + return { ...atom, tags: unique } + }) - const uniqueTags = Array.from( - new Map(tags.map((tag) => [tag.term_id, tag])).values() - ) + setClaims(extractedClaims) + setAtomsWithTags(withTags) + setIsLoading(false) + } - return { - ...atom, - tags: uniqueTags + chrome.storage.local.get(KEY, (items) => { + const stored = items[KEY] + if (stored && stored.uri && stored.data) { + process(stored.uri, stored.data) + } else { + setIsLoading(false) } }) - console.log("Tags :", atomsWithTags) + const onChange = ( + changes: Record, + areaName: string + ) => { + if (areaName !== "local" || !changes[KEY]) return + const { uri, data } = changes[KEY].newValue + process(uri, data) + } + chrome.storage.onChanged.addListener(onChange) + + return () => { + chrome.storage.onChanged.removeListener(onChange) + } +}, [currentUrl]) + const tabs = [ { label: "Claims", content: (
- {loading ? ( + {isLoading ? ( "Loading..." - ) : typeof data !== "undefined" && claims.length !== 0 ? ( + ) : claims.length !== 0 ? ( claims.map( (claim, index) => ( - console.log(claim), - ( - - ) + ) ) ) : ( @@ -161,14 +147,12 @@ function Home() { label: "Atoms", content: (
- {loading ? ( + {isLoading ? ( "Loading..." - ) : typeof data !== "undefined" && atoms.length != 0 ? ( - atomsWithTags.map((atom) => { - return ( - - ) - }) + ) : atomsWithTags.length !== 0 ? ( + atomsWithTags.map((atom) => ( + + )) ) : (

@@ -215,12 +199,7 @@ function Home() {

- {error && ( -

- An error occurred while requesting this page. -

- )} - + -
- - -
- + +
+ + +
+ +
+ +
- - -
+ ) diff --git a/src/sidepanel/index.tsx b/src/sidepanel/index.tsx index 764450f2..aaba1b99 100644 --- a/src/sidepanel/index.tsx +++ b/src/sidepanel/index.tsx @@ -5,9 +5,14 @@ import ParticlesCanvas from "~src/components/ui/ParticulBg/ParticlesCanvas" import GroupParticlesCanvas from "~src/components/ui/ParticulBg/GroupParticlesCanvas" import { ThemeProvider } from "~src/components/ThemeProvider" import { NavigationProvider } from "~src/components/layout/NavigationProvider" +import { BrowserRouter as Router } from "react-router-dom" function IndexSidepanel() { + useEffect(() => { + chrome.runtime.sendMessage({ action: "SIDEPANEL_READY" }); + }, []); + useEffect(() => { umami("Open Side Panel") const port = chrome.runtime.connect({ name: "sidepanel" }) @@ -29,11 +34,13 @@ function IndexSidepanel() { <> -
- - - -
+ +
+ + + +
+