mirror of
https://github.com/luminous-devs/lume.git
synced 2024-10-02 09:50:47 +00:00
wip: fully migrate to ark
This commit is contained in:
parent
6f5ea1229d
commit
e507187044
@ -80,7 +80,6 @@
|
|||||||
"react-hotkeys-hook": "^4.4.1",
|
"react-hotkeys-hook": "^4.4.1",
|
||||||
"react-router-dom": "^6.20.1",
|
"react-router-dom": "^6.20.1",
|
||||||
"react-string-replace": "^1.1.1",
|
"react-string-replace": "^1.1.1",
|
||||||
"reactflow": "^11.10.1",
|
|
||||||
"sonner": "^1.2.4",
|
"sonner": "^1.2.4",
|
||||||
"tauri-controls": "github:reyamir/tauri-controls",
|
"tauri-controls": "github:reyamir/tauri-controls",
|
||||||
"tippy.js": "^6.3.7",
|
"tippy.js": "^6.3.7",
|
||||||
|
382
pnpm-lock.yaml
382
pnpm-lock.yaml
@ -191,9 +191,6 @@ dependencies:
|
|||||||
react-string-replace:
|
react-string-replace:
|
||||||
specifier: ^1.1.1
|
specifier: ^1.1.1
|
||||||
version: 1.1.1
|
version: 1.1.1
|
||||||
reactflow:
|
|
||||||
specifier: ^11.10.1
|
|
||||||
version: 11.10.1(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
sonner:
|
sonner:
|
||||||
specifier: ^1.2.4
|
specifier: ^1.2.4
|
||||||
version: 1.2.4(react-dom@18.2.0)(react@18.2.0)
|
version: 1.2.4(react-dom@18.2.0)(react@18.2.0)
|
||||||
@ -1752,114 +1749,6 @@ packages:
|
|||||||
'@babel/runtime': 7.23.5
|
'@babel/runtime': 7.23.5
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@reactflow/background@11.3.6(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0):
|
|
||||||
resolution: {integrity: sha512-06FPlSUOOMALEEs+2PqPAbpqmL7WDjrkbG2UsDr2d6mbcDDhHiV4tu9FYoz44SQvXo7ma9VRotlsaR4OiRcYsg==}
|
|
||||||
peerDependencies:
|
|
||||||
react: '>=17'
|
|
||||||
react-dom: '>=17'
|
|
||||||
dependencies:
|
|
||||||
'@reactflow/core': 11.10.1(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
classcat: 5.0.4
|
|
||||||
react: 18.2.0
|
|
||||||
react-dom: 18.2.0(react@18.2.0)
|
|
||||||
zustand: 4.4.7(@types/react@18.2.42)(react@18.2.0)
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- '@types/react'
|
|
||||||
- immer
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@reactflow/controls@11.2.6(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0):
|
|
||||||
resolution: {integrity: sha512-4QHT92/ACVlZkvV+Hq44bAPV8WbMhkJl+/J0EbXcqQ1+an7cWJsF84eeelJw7R5J76RoaSSpKdsWsL2v7HAVlw==}
|
|
||||||
peerDependencies:
|
|
||||||
react: '>=17'
|
|
||||||
react-dom: '>=17'
|
|
||||||
dependencies:
|
|
||||||
'@reactflow/core': 11.10.1(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
classcat: 5.0.4
|
|
||||||
react: 18.2.0
|
|
||||||
react-dom: 18.2.0(react@18.2.0)
|
|
||||||
zustand: 4.4.7(@types/react@18.2.42)(react@18.2.0)
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- '@types/react'
|
|
||||||
- immer
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@reactflow/core@11.10.1(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0):
|
|
||||||
resolution: {integrity: sha512-GIh3usY1W3eVobx//OO9+Cwm+5evQBBdPGxDaeXwm25UqPMWRI240nXQA5F/5gL5Mwpf0DUC7DR2EmrKNQy+Rw==}
|
|
||||||
peerDependencies:
|
|
||||||
react: '>=17'
|
|
||||||
react-dom: '>=17'
|
|
||||||
dependencies:
|
|
||||||
'@types/d3': 7.4.3
|
|
||||||
'@types/d3-drag': 3.0.7
|
|
||||||
'@types/d3-selection': 3.0.10
|
|
||||||
'@types/d3-zoom': 3.0.8
|
|
||||||
classcat: 5.0.4
|
|
||||||
d3-drag: 3.0.0
|
|
||||||
d3-selection: 3.0.0
|
|
||||||
d3-zoom: 3.0.0
|
|
||||||
react: 18.2.0
|
|
||||||
react-dom: 18.2.0(react@18.2.0)
|
|
||||||
zustand: 4.4.7(@types/react@18.2.42)(react@18.2.0)
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- '@types/react'
|
|
||||||
- immer
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@reactflow/minimap@11.7.6(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0):
|
|
||||||
resolution: {integrity: sha512-kJEtyeQkTZYViLGebVWHVUJROMAGcvejvT+iX4DqKnFb5yK8E8LWlXQpRx2FrL9gDy80mJJaciy7IxnnQKE1bg==}
|
|
||||||
peerDependencies:
|
|
||||||
react: '>=17'
|
|
||||||
react-dom: '>=17'
|
|
||||||
dependencies:
|
|
||||||
'@reactflow/core': 11.10.1(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
'@types/d3-selection': 3.0.10
|
|
||||||
'@types/d3-zoom': 3.0.8
|
|
||||||
classcat: 5.0.4
|
|
||||||
d3-selection: 3.0.0
|
|
||||||
d3-zoom: 3.0.0
|
|
||||||
react: 18.2.0
|
|
||||||
react-dom: 18.2.0(react@18.2.0)
|
|
||||||
zustand: 4.4.7(@types/react@18.2.42)(react@18.2.0)
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- '@types/react'
|
|
||||||
- immer
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@reactflow/node-resizer@2.2.6(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0):
|
|
||||||
resolution: {integrity: sha512-1Xb6q97uP7hRBLpog9sRCNfnsHdDgFRGEiU+lQqGgPEAeYwl4nRjWa/sXwH6ajniKxBhGEvrdzOgEFn6CRMcpQ==}
|
|
||||||
peerDependencies:
|
|
||||||
react: '>=17'
|
|
||||||
react-dom: '>=17'
|
|
||||||
dependencies:
|
|
||||||
'@reactflow/core': 11.10.1(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
classcat: 5.0.4
|
|
||||||
d3-drag: 3.0.0
|
|
||||||
d3-selection: 3.0.0
|
|
||||||
react: 18.2.0
|
|
||||||
react-dom: 18.2.0(react@18.2.0)
|
|
||||||
zustand: 4.4.7(@types/react@18.2.42)(react@18.2.0)
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- '@types/react'
|
|
||||||
- immer
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@reactflow/node-toolbar@1.3.6(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0):
|
|
||||||
resolution: {integrity: sha512-JXDEuZ0wKjZ8z7qK2bIst0eZPzNyVEsiHL0e93EyuqT4fA9icoyE0fLq2ryNOOp7MXgId1h7LusnH6ta45F0yQ==}
|
|
||||||
peerDependencies:
|
|
||||||
react: '>=17'
|
|
||||||
react-dom: '>=17'
|
|
||||||
dependencies:
|
|
||||||
'@reactflow/core': 11.10.1(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
classcat: 5.0.4
|
|
||||||
react: 18.2.0
|
|
||||||
react-dom: 18.2.0(react@18.2.0)
|
|
||||||
zustand: 4.4.7(@types/react@18.2.42)(react@18.2.0)
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- '@types/react'
|
|
||||||
- immer
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@remirror/core-constants@2.0.2:
|
/@remirror/core-constants@2.0.2:
|
||||||
resolution: {integrity: sha512-dyHY+sMF0ihPus3O27ODd4+agdHMEmuRdyiZJ2CCWjPV5UFmn17ZbElvk6WOGVE4rdCJKZQCrPV2BcikOMLUGQ==}
|
resolution: {integrity: sha512-dyHY+sMF0ihPus3O27ODd4+agdHMEmuRdyiZJ2CCWjPV5UFmn17ZbElvk6WOGVE4rdCJKZQCrPV2BcikOMLUGQ==}
|
||||||
dev: false
|
dev: false
|
||||||
@ -2598,189 +2487,6 @@ packages:
|
|||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@types/d3-array@3.2.1:
|
|
||||||
resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/d3-axis@3.0.6:
|
|
||||||
resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==}
|
|
||||||
dependencies:
|
|
||||||
'@types/d3-selection': 3.0.10
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/d3-brush@3.0.6:
|
|
||||||
resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==}
|
|
||||||
dependencies:
|
|
||||||
'@types/d3-selection': 3.0.10
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/d3-chord@3.0.6:
|
|
||||||
resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/d3-color@3.1.3:
|
|
||||||
resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/d3-contour@3.0.6:
|
|
||||||
resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==}
|
|
||||||
dependencies:
|
|
||||||
'@types/d3-array': 3.2.1
|
|
||||||
'@types/geojson': 7946.0.13
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/d3-delaunay@6.0.4:
|
|
||||||
resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/d3-dispatch@3.0.6:
|
|
||||||
resolution: {integrity: sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/d3-drag@3.0.7:
|
|
||||||
resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==}
|
|
||||||
dependencies:
|
|
||||||
'@types/d3-selection': 3.0.10
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/d3-dsv@3.0.7:
|
|
||||||
resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/d3-ease@3.0.2:
|
|
||||||
resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/d3-fetch@3.0.7:
|
|
||||||
resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==}
|
|
||||||
dependencies:
|
|
||||||
'@types/d3-dsv': 3.0.7
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/d3-force@3.0.9:
|
|
||||||
resolution: {integrity: sha512-IKtvyFdb4Q0LWna6ymywQsEYjK/94SGhPrMfEr1TIc5OBeziTi+1jcCvttts8e0UWZIxpasjnQk9MNk/3iS+kA==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/d3-format@3.0.4:
|
|
||||||
resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/d3-geo@3.1.0:
|
|
||||||
resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==}
|
|
||||||
dependencies:
|
|
||||||
'@types/geojson': 7946.0.13
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/d3-hierarchy@3.1.6:
|
|
||||||
resolution: {integrity: sha512-qlmD/8aMk5xGorUvTUWHCiumvgaUXYldYjNVOWtYoTYY/L+WwIEAmJxUmTgr9LoGNG0PPAOmqMDJVDPc7DOpPw==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/d3-interpolate@3.0.4:
|
|
||||||
resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==}
|
|
||||||
dependencies:
|
|
||||||
'@types/d3-color': 3.1.3
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/d3-path@3.0.2:
|
|
||||||
resolution: {integrity: sha512-WAIEVlOCdd/NKRYTsqCpOMHQHemKBEINf8YXMYOtXH0GA7SY0dqMB78P3Uhgfy+4X+/Mlw2wDtlETkN6kQUCMA==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/d3-polygon@3.0.2:
|
|
||||||
resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/d3-quadtree@3.0.6:
|
|
||||||
resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/d3-random@3.0.3:
|
|
||||||
resolution: {integrity: sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/d3-scale-chromatic@3.0.3:
|
|
||||||
resolution: {integrity: sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/d3-scale@4.0.8:
|
|
||||||
resolution: {integrity: sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==}
|
|
||||||
dependencies:
|
|
||||||
'@types/d3-time': 3.0.3
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/d3-selection@3.0.10:
|
|
||||||
resolution: {integrity: sha512-cuHoUgS/V3hLdjJOLTT691+G2QoqAjCVLmr4kJXR4ha56w1Zdu8UUQ5TxLRqudgNjwXeQxKMq4j+lyf9sWuslg==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/d3-shape@3.1.6:
|
|
||||||
resolution: {integrity: sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==}
|
|
||||||
dependencies:
|
|
||||||
'@types/d3-path': 3.0.2
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/d3-time-format@4.0.3:
|
|
||||||
resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/d3-time@3.0.3:
|
|
||||||
resolution: {integrity: sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/d3-timer@3.0.2:
|
|
||||||
resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/d3-transition@3.0.8:
|
|
||||||
resolution: {integrity: sha512-ew63aJfQ/ms7QQ4X7pk5NxQ9fZH/z+i24ZfJ6tJSfqxJMrYLiK01EAs2/Rtw/JreGUsS3pLPNV644qXFGnoZNQ==}
|
|
||||||
dependencies:
|
|
||||||
'@types/d3-selection': 3.0.10
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/d3-zoom@3.0.8:
|
|
||||||
resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==}
|
|
||||||
dependencies:
|
|
||||||
'@types/d3-interpolate': 3.0.4
|
|
||||||
'@types/d3-selection': 3.0.10
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/d3@7.4.3:
|
|
||||||
resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==}
|
|
||||||
dependencies:
|
|
||||||
'@types/d3-array': 3.2.1
|
|
||||||
'@types/d3-axis': 3.0.6
|
|
||||||
'@types/d3-brush': 3.0.6
|
|
||||||
'@types/d3-chord': 3.0.6
|
|
||||||
'@types/d3-color': 3.1.3
|
|
||||||
'@types/d3-contour': 3.0.6
|
|
||||||
'@types/d3-delaunay': 6.0.4
|
|
||||||
'@types/d3-dispatch': 3.0.6
|
|
||||||
'@types/d3-drag': 3.0.7
|
|
||||||
'@types/d3-dsv': 3.0.7
|
|
||||||
'@types/d3-ease': 3.0.2
|
|
||||||
'@types/d3-fetch': 3.0.7
|
|
||||||
'@types/d3-force': 3.0.9
|
|
||||||
'@types/d3-format': 3.0.4
|
|
||||||
'@types/d3-geo': 3.1.0
|
|
||||||
'@types/d3-hierarchy': 3.1.6
|
|
||||||
'@types/d3-interpolate': 3.0.4
|
|
||||||
'@types/d3-path': 3.0.2
|
|
||||||
'@types/d3-polygon': 3.0.2
|
|
||||||
'@types/d3-quadtree': 3.0.6
|
|
||||||
'@types/d3-random': 3.0.3
|
|
||||||
'@types/d3-scale': 4.0.8
|
|
||||||
'@types/d3-scale-chromatic': 3.0.3
|
|
||||||
'@types/d3-selection': 3.0.10
|
|
||||||
'@types/d3-shape': 3.1.6
|
|
||||||
'@types/d3-time': 3.0.3
|
|
||||||
'@types/d3-time-format': 4.0.3
|
|
||||||
'@types/d3-timer': 3.0.2
|
|
||||||
'@types/d3-transition': 3.0.8
|
|
||||||
'@types/d3-zoom': 3.0.8
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/geojson@7946.0.13:
|
|
||||||
resolution: {integrity: sha512-bmrNrgKMOhM3WsafmbGmC+6dsF2Z308vLFsQ3a/bT8X8Sv5clVYpPars/UPq+sAaJP+5OoLAYgwbkS5QEJdLUQ==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/html-to-text@9.0.4:
|
/@types/html-to-text@9.0.4:
|
||||||
resolution: {integrity: sha512-pUY3cKH/Nm2yYrEmDlPR1mR7yszjGx4DrwPjQ702C4/D5CwHuZTgZdIdwPkRbcuhs7BAh2L5rg3CL5cbRiGTCQ==}
|
resolution: {integrity: sha512-pUY3cKH/Nm2yYrEmDlPR1mR7yszjGx4DrwPjQ702C4/D5CwHuZTgZdIdwPkRbcuhs7BAh2L5rg3CL5cbRiGTCQ==}
|
||||||
dev: true
|
dev: true
|
||||||
@ -3307,10 +3013,6 @@ packages:
|
|||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/classcat@5.0.4:
|
|
||||||
resolution: {integrity: sha512-sbpkOw6z413p+HDGcBENe498WM9woqWHiJxCq7nvmxe9WmrUmqfAcxpIwAiMtM5Q3AhYkzXcNQHqsWq0mND51g==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/cli-cursor@4.0.0:
|
/cli-cursor@4.0.0:
|
||||||
resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==}
|
resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==}
|
||||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||||
@ -3403,71 +3105,6 @@ packages:
|
|||||||
/csstype@3.1.3:
|
/csstype@3.1.3:
|
||||||
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
|
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
|
||||||
|
|
||||||
/d3-color@3.1.0:
|
|
||||||
resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/d3-dispatch@3.0.1:
|
|
||||||
resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/d3-drag@3.0.0:
|
|
||||||
resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
dependencies:
|
|
||||||
d3-dispatch: 3.0.1
|
|
||||||
d3-selection: 3.0.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/d3-ease@3.0.1:
|
|
||||||
resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/d3-interpolate@3.0.1:
|
|
||||||
resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
dependencies:
|
|
||||||
d3-color: 3.1.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/d3-selection@3.0.0:
|
|
||||||
resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/d3-timer@3.0.1:
|
|
||||||
resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/d3-transition@3.0.1(d3-selection@3.0.0):
|
|
||||||
resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
peerDependencies:
|
|
||||||
d3-selection: 2 - 3
|
|
||||||
dependencies:
|
|
||||||
d3-color: 3.1.0
|
|
||||||
d3-dispatch: 3.0.1
|
|
||||||
d3-ease: 3.0.1
|
|
||||||
d3-interpolate: 3.0.1
|
|
||||||
d3-selection: 3.0.0
|
|
||||||
d3-timer: 3.0.1
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/d3-zoom@3.0.0:
|
|
||||||
resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
dependencies:
|
|
||||||
d3-dispatch: 3.0.1
|
|
||||||
d3-drag: 3.0.0
|
|
||||||
d3-interpolate: 3.0.1
|
|
||||||
d3-selection: 3.0.0
|
|
||||||
d3-transition: 3.0.1(d3-selection@3.0.0)
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/d@1.0.1:
|
/d@1.0.1:
|
||||||
resolution: {integrity: sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==}
|
resolution: {integrity: sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -5585,25 +5222,6 @@ packages:
|
|||||||
loose-envify: 1.4.0
|
loose-envify: 1.4.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/reactflow@11.10.1(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0):
|
|
||||||
resolution: {integrity: sha512-Q616fElAc5/N37tMwjuRkkgm/VgmnLLTNNCj61z5mvJxae+/VXZQMfot1K6a5LLz9G3SVKqU97PMb9Ga1PRXew==}
|
|
||||||
peerDependencies:
|
|
||||||
react: '>=17'
|
|
||||||
react-dom: '>=17'
|
|
||||||
dependencies:
|
|
||||||
'@reactflow/background': 11.3.6(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
'@reactflow/controls': 11.2.6(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
'@reactflow/core': 11.10.1(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
'@reactflow/minimap': 11.7.6(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
'@reactflow/node-resizer': 2.2.6(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
'@reactflow/node-toolbar': 1.3.6(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
react: 18.2.0
|
|
||||||
react-dom: 18.2.0(react@18.2.0)
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- '@types/react'
|
|
||||||
- immer
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/read-cache@1.0.0:
|
/read-cache@1.0.0:
|
||||||
resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
|
resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
/* @import 'reactflow/dist/style.css'; */
|
|
||||||
|
|
||||||
/* Vidstack */
|
/* Vidstack */
|
||||||
@import '@vidstack/react/player/styles/default/theme.css';
|
@import '@vidstack/react/player/styles/default/theme.css';
|
||||||
@import '@vidstack/react/player/styles/default/layouts/video.css';
|
@import '@vidstack/react/player/styles/default/layouts/video.css';
|
||||||
|
11
src/app.tsx
11
src/app.tsx
@ -1,11 +1,9 @@
|
|||||||
import { message } from '@tauri-apps/plugin-dialog';
|
import { message } from '@tauri-apps/plugin-dialog';
|
||||||
import { fetch } from '@tauri-apps/plugin-http';
|
import { fetch } from '@tauri-apps/plugin-http';
|
||||||
import { RouterProvider, createBrowserRouter, defer, redirect } from 'react-router-dom';
|
import { RouterProvider, createBrowserRouter, defer, redirect } from 'react-router-dom';
|
||||||
import { ReactFlowProvider } from 'reactflow';
|
|
||||||
|
|
||||||
import { ChatsScreen } from '@app/chats';
|
import { ChatsScreen } from '@app/chats';
|
||||||
import { ErrorScreen } from '@app/error';
|
import { ErrorScreen } from '@app/error';
|
||||||
import { ExploreScreen } from '@app/explore';
|
|
||||||
|
|
||||||
import { useArk } from '@libs/ark';
|
import { useArk } from '@libs/ark';
|
||||||
|
|
||||||
@ -87,15 +85,6 @@ export default function App() {
|
|||||||
return { Component: RelayScreen };
|
return { Component: RelayScreen };
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: 'explore',
|
|
||||||
element: (
|
|
||||||
<ReactFlowProvider>
|
|
||||||
<ExploreScreen />
|
|
||||||
</ReactFlowProvider>
|
|
||||||
),
|
|
||||||
errorElement: <ErrorScreen />,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: 'chats',
|
path: 'chats',
|
||||||
element: <ChatsScreen />,
|
element: <ChatsScreen />,
|
||||||
|
@ -12,16 +12,13 @@ import { useArk } from '@libs/ark';
|
|||||||
import { LoaderIcon } from '@shared/icons';
|
import { LoaderIcon } from '@shared/icons';
|
||||||
import { User } from '@shared/user';
|
import { User } from '@shared/user';
|
||||||
|
|
||||||
import { useNostr } from '@utils/hooks/useNostr';
|
|
||||||
|
|
||||||
export function ChatScreen() {
|
export function ChatScreen() {
|
||||||
const { ark } = useArk();
|
const { ark } = useArk();
|
||||||
const { pubkey } = useParams();
|
const { pubkey } = useParams();
|
||||||
const { fetchNIP04Messages } = useNostr();
|
|
||||||
const { status, data } = useQuery({
|
const { status, data } = useQuery({
|
||||||
queryKey: ['nip04-dm', pubkey],
|
queryKey: ['nip04-dm', pubkey],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
return await fetchNIP04Messages(pubkey);
|
return await ark.getAllMessagesByPubkey({ pubkey });
|
||||||
},
|
},
|
||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
});
|
});
|
||||||
|
@ -5,16 +5,16 @@ import { Outlet } from 'react-router-dom';
|
|||||||
|
|
||||||
import { ChatListItem } from '@app/chats/components/chatListItem';
|
import { ChatListItem } from '@app/chats/components/chatListItem';
|
||||||
|
|
||||||
|
import { useArk } from '@libs/ark';
|
||||||
|
|
||||||
import { LoaderIcon } from '@shared/icons';
|
import { LoaderIcon } from '@shared/icons';
|
||||||
|
|
||||||
import { useNostr } from '@utils/hooks/useNostr';
|
|
||||||
|
|
||||||
export function ChatsScreen() {
|
export function ChatsScreen() {
|
||||||
const { getAllNIP04Chats } = useNostr();
|
const { ark } = useArk();
|
||||||
const { status, data } = useQuery({
|
const { status, data } = useQuery({
|
||||||
queryKey: ['nip04-chats'],
|
queryKey: ['nip04-chats'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
return await getAllNIP04Chats();
|
return await ark.getAllChats();
|
||||||
},
|
},
|
||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
refetchOnMount: false,
|
refetchOnMount: false,
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
import { BaseEdge, EdgeProps, getBezierPath } from 'reactflow';
|
|
||||||
|
|
||||||
export function Edge({
|
|
||||||
sourceX,
|
|
||||||
sourceY,
|
|
||||||
targetX,
|
|
||||||
targetY,
|
|
||||||
sourcePosition,
|
|
||||||
targetPosition,
|
|
||||||
style = {},
|
|
||||||
markerEnd,
|
|
||||||
}: EdgeProps) {
|
|
||||||
const [edgePath] = getBezierPath({
|
|
||||||
sourceX,
|
|
||||||
sourceY,
|
|
||||||
sourcePosition,
|
|
||||||
targetX,
|
|
||||||
targetY,
|
|
||||||
targetPosition,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BaseEdge
|
|
||||||
path={edgePath}
|
|
||||||
markerEnd={markerEnd}
|
|
||||||
style={{ ...style, stroke: '#71717a' }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
import { memo } from 'react';
|
|
||||||
|
|
||||||
import { useProfile } from '@utils/hooks/useProfile';
|
|
||||||
|
|
||||||
export const GroupTitle = memo(function GroupTitle({ pubkey }: { pubkey: string }) {
|
|
||||||
const { isLoading, user } = useProfile(pubkey);
|
|
||||||
|
|
||||||
if (isLoading) {
|
|
||||||
return <div className="h-3 w-24 animate-pulse rounded bg-white/10" />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<h3 className="text-sm font-semibold text-blue-500">{`${
|
|
||||||
user.name || user.display_name
|
|
||||||
}'s network`}</h3>
|
|
||||||
);
|
|
||||||
});
|
|
@ -1,14 +0,0 @@
|
|||||||
export function Line({ fromX, fromY, toX, toY }) {
|
|
||||||
return (
|
|
||||||
<g>
|
|
||||||
<path
|
|
||||||
fill="none"
|
|
||||||
stroke="#f5d0fe"
|
|
||||||
strokeWidth={1.5}
|
|
||||||
className="animated"
|
|
||||||
d={`M${fromX},${fromY} C ${fromX} ${toY} ${fromX} ${toY} ${toX},${toY}`}
|
|
||||||
/>
|
|
||||||
<circle cx={toX} cy={toY} fill="#fff" r={3} stroke="#f5d0fe" strokeWidth={1.5} />
|
|
||||||
</g>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
import { Handle, Position } from 'reactflow';
|
|
||||||
|
|
||||||
import { UserWithDrawer } from '@app/explore/components/userWithDrawer';
|
|
||||||
|
|
||||||
import { GroupTitle } from './groupTitle';
|
|
||||||
|
|
||||||
export function UserGroupNode({ data }) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Handle
|
|
||||||
type="target"
|
|
||||||
position={Position.Top}
|
|
||||||
className="h-2 w-5 rounded-full border-none !bg-blue-400"
|
|
||||||
/>
|
|
||||||
<div className="relative mx-3 my-3 flex flex-col gap-1">
|
|
||||||
{data.title ? (
|
|
||||||
<h3 className="text-sm font-semibold text-blue-500">{data.title}</h3>
|
|
||||||
) : (
|
|
||||||
<GroupTitle pubkey={data.pubkey} />
|
|
||||||
)}
|
|
||||||
<div className="grid grid-cols-5 gap-6 rounded-lg border border-blue-500/50 bg-blue-500/10 p-4">
|
|
||||||
{data.list.map((user: string) => (
|
|
||||||
<UserWithDrawer key={user} pubkey={user} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Handle
|
|
||||||
type="source"
|
|
||||||
position={Position.Bottom}
|
|
||||||
className="h-2 w-5 rounded-full border-none !bg-blue-400"
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,59 +0,0 @@
|
|||||||
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
import { useCallback } from 'react';
|
|
||||||
|
|
||||||
import { LoaderIcon } from '@shared/icons';
|
|
||||||
import { MemoizedRepost, MemoizedTextNote, UnknownNote } from '@shared/notes';
|
|
||||||
|
|
||||||
import { useNostr } from '@utils/hooks/useNostr';
|
|
||||||
|
|
||||||
export function UserLatestPosts({ pubkey }: { pubkey: string }) {
|
|
||||||
const { getEventsByPubkey } = useNostr();
|
|
||||||
const { status, data } = useQuery({
|
|
||||||
queryKey: ['user-posts', pubkey],
|
|
||||||
queryFn: async () => {
|
|
||||||
return await getEventsByPubkey(pubkey);
|
|
||||||
},
|
|
||||||
refetchOnWindowFocus: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const renderItem = useCallback(
|
|
||||||
(event: NDKEvent) => {
|
|
||||||
switch (event.kind) {
|
|
||||||
case NDKKind.Text:
|
|
||||||
return <MemoizedTextNote key={event.id} event={event} />;
|
|
||||||
case NDKKind.Repost:
|
|
||||||
return <MemoizedRepost key={event.id} event={event} />;
|
|
||||||
default:
|
|
||||||
return <UnknownNote key={event.id} event={event} />;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[data]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="mt-4 border-t border-neutral-300 pt-3 dark:border-neutral-700">
|
|
||||||
<h3 className="mb-4 px-3 text-lg font-semibold text-neutral-900 dark:text-neutral-100">
|
|
||||||
Latest post
|
|
||||||
</h3>
|
|
||||||
<div>
|
|
||||||
{status === 'pending' ? (
|
|
||||||
<div className="px-3">
|
|
||||||
<div className="inline-flex h-16 w-full items-center justify-center gap-1.5 rounded-lg bg-neutral-300 text-sm font-medium dark:bg-neutral-700">
|
|
||||||
<LoaderIcon className="h-4 w-4 animate-spin" />
|
|
||||||
Loading latest posts...
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : data.length < 1 ? (
|
|
||||||
<div className="px-3">
|
|
||||||
<div className="inline-flex h-16 w-full items-center justify-center rounded-lg bg-neutral-300 text-sm font-medium dark:bg-neutral-700">
|
|
||||||
No posts from 24 hours ago
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
data.map((event) => renderItem(event))
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
import { Handle, Position } from 'reactflow';
|
|
||||||
|
|
||||||
import { User } from '@shared/user';
|
|
||||||
|
|
||||||
export function UserNode({ data }) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="relative mx-3 my-3 inline-flex h-12 w-12 shrink-0 items-center justify-center">
|
|
||||||
<span className="absolute inline-flex h-8 w-8 animate-ping rounded-lg bg-green-400 opacity-75"></span>
|
|
||||||
<div className="relative z-10">
|
|
||||||
<User pubkey={data.pubkey} variant="avatar" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Handle
|
|
||||||
type="source"
|
|
||||||
position={Position.Bottom}
|
|
||||||
className="h-2 w-2 rounded-full border-none !bg-white/20"
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,155 +0,0 @@
|
|||||||
import { NDKEvent, NDKKind, NDKUser } from '@nostr-dev-kit/ndk';
|
|
||||||
import * as Dialog from '@radix-ui/react-dialog';
|
|
||||||
import { memo, useEffect, useState } from 'react';
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import { toast } from 'sonner';
|
|
||||||
|
|
||||||
import { useNDK } from '@libs/ndk/provider';
|
|
||||||
import { useStorage } from '@libs/storage/provider';
|
|
||||||
|
|
||||||
import { NIP05 } from '@shared/nip05';
|
|
||||||
import { TextNote } from '@shared/notes';
|
|
||||||
import { User } from '@shared/user';
|
|
||||||
|
|
||||||
import { useProfile } from '@utils/hooks/useProfile';
|
|
||||||
import { displayNpub } from '@utils/shortenKey';
|
|
||||||
|
|
||||||
import { UserLatestPosts } from './userLatestPosts';
|
|
||||||
|
|
||||||
export const UserWithDrawer = memo(function UserWithDrawer({
|
|
||||||
pubkey,
|
|
||||||
}: {
|
|
||||||
pubkey: string;
|
|
||||||
}) {
|
|
||||||
const { db } = useStorage();
|
|
||||||
const { ndk } = useNDK();
|
|
||||||
const { isLoading, user } = useProfile(pubkey);
|
|
||||||
|
|
||||||
const [followed, setFollowed] = useState(false);
|
|
||||||
|
|
||||||
const follow = async (pubkey: string) => {
|
|
||||||
try {
|
|
||||||
const user = ndk.getUser({ pubkey: db.account.pubkey });
|
|
||||||
const contacts = await user.follows();
|
|
||||||
const add = await user.follow(new NDKUser({ pubkey: pubkey }), contacts);
|
|
||||||
|
|
||||||
if (add) {
|
|
||||||
setFollowed(true);
|
|
||||||
} else {
|
|
||||||
toast('You already follow this user');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const unfollow = async (pubkey: string) => {
|
|
||||||
try {
|
|
||||||
const user = ndk.getUser({ pubkey: db.account.pubkey });
|
|
||||||
const contacts = await user.follows();
|
|
||||||
contacts.delete(new NDKUser({ pubkey: pubkey }));
|
|
||||||
|
|
||||||
let list: string[][];
|
|
||||||
contacts.forEach((el) => list.push(['p', el.pubkey, el.relayUrls?.[0] || '', '']));
|
|
||||||
|
|
||||||
const event = new NDKEvent(ndk);
|
|
||||||
event.content = '';
|
|
||||||
event.kind = NDKKind.Contacts;
|
|
||||||
event.tags = list;
|
|
||||||
|
|
||||||
const publishedRelays = await event.publish();
|
|
||||||
if (publishedRelays) {
|
|
||||||
setFollowed(false);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (db.account.contacts.includes(pubkey)) {
|
|
||||||
setFollowed(true);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog.Root>
|
|
||||||
<Dialog.Trigger asChild>
|
|
||||||
<button type="button">
|
|
||||||
<User pubkey={pubkey} variant="avatar" />
|
|
||||||
</button>
|
|
||||||
</Dialog.Trigger>
|
|
||||||
<Dialog.Portal>
|
|
||||||
<Dialog.Content className="fixed right-0 top-0 z-50 flex h-full w-[400px] animate-slideRightAndFade items-center justify-center px-4 pb-4 pt-16 transition-all">
|
|
||||||
<div className="h-full w-full overflow-y-auto rounded-lg border border-neutral-300 bg-neutral-200 py-3 dark:border-neutral-700 dark:bg-neutral-800">
|
|
||||||
{isLoading ? (
|
|
||||||
<div>
|
|
||||||
<p>Loading...</p>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<div className="flex flex-col gap-3 px-3">
|
|
||||||
<img
|
|
||||||
src={user?.picture || user?.image}
|
|
||||||
alt={pubkey}
|
|
||||||
loading="lazy"
|
|
||||||
decoding="async"
|
|
||||||
style={{ contentVisibility: 'auto' }}
|
|
||||||
className="h-12 w-12 rounded-lg"
|
|
||||||
/>
|
|
||||||
<div className="flex flex-1 flex-col gap-2">
|
|
||||||
<div className="flex flex-col gap-1.5">
|
|
||||||
<div>
|
|
||||||
<h5 className="text-lg font-semibold">
|
|
||||||
{user?.name || user?.display_name || user?.displayName}
|
|
||||||
</h5>
|
|
||||||
{user?.nip05 ? (
|
|
||||||
<NIP05
|
|
||||||
pubkey={pubkey}
|
|
||||||
nip05={user?.nip05}
|
|
||||||
className="max-w-[15rem] truncate text-sm text-neutral-600 dark:text-neutral-400"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<span className="max-w-[15rem] truncate text-sm text-neutral-600 dark:text-neutral-400">
|
|
||||||
{displayNpub(pubkey, 16)}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{user?.about ? <TextNote content={user?.about} /> : null}
|
|
||||||
</div>
|
|
||||||
<div className="inline-flex items-center gap-2">
|
|
||||||
{followed ? (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => unfollow(pubkey)}
|
|
||||||
className="inline-flex h-9 w-36 items-center justify-center rounded-lg bg-neutral-300 text-sm font-medium hover:bg-blue-500 hover:text-white dark:bg-neutral-700"
|
|
||||||
>
|
|
||||||
Unfollow
|
|
||||||
</button>
|
|
||||||
) : (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => follow(pubkey)}
|
|
||||||
className="inline-flex h-9 w-36 items-center justify-center rounded-lg bg-neutral-300 text-sm font-medium hover:bg-blue-500 hover:text-white dark:bg-neutral-700"
|
|
||||||
>
|
|
||||||
Follow
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
<Link
|
|
||||||
to={`/chats/${pubkey}`}
|
|
||||||
className="inline-flex h-9 w-36 items-center justify-center rounded-lg bg-neutral-300 text-sm font-medium hover:bg-blue-500 hover:text-white dark:bg-neutral-700"
|
|
||||||
>
|
|
||||||
Message
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<UserLatestPosts pubkey={pubkey} />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Dialog.Content>
|
|
||||||
</Dialog.Portal>
|
|
||||||
</Dialog.Root>
|
|
||||||
);
|
|
||||||
});
|
|
@ -1,116 +0,0 @@
|
|||||||
import { useCallback, useMemo, useRef } from 'react';
|
|
||||||
import ReactFlow, {
|
|
||||||
Background,
|
|
||||||
ConnectionMode,
|
|
||||||
addEdge,
|
|
||||||
useEdgesState,
|
|
||||||
useNodesState,
|
|
||||||
useReactFlow,
|
|
||||||
} from 'reactflow';
|
|
||||||
|
|
||||||
import { Edge } from '@app/explore/components/edge';
|
|
||||||
import { Line } from '@app/explore/components/line';
|
|
||||||
import { UserGroupNode } from '@app/explore/components/userGroupNode';
|
|
||||||
import { UserNode } from '@app/explore/components/userNode';
|
|
||||||
|
|
||||||
import { useStorage } from '@libs/storage/provider';
|
|
||||||
|
|
||||||
import { useNostr } from '@utils/hooks/useNostr';
|
|
||||||
import { getMultipleRandom } from '@utils/transform';
|
|
||||||
|
|
||||||
let id = 2;
|
|
||||||
const getId = () => `${id++}`;
|
|
||||||
const nodeTypes = { user: UserNode, userGroup: UserGroupNode };
|
|
||||||
const edgeTypes = { buttonedge: Edge };
|
|
||||||
|
|
||||||
export function ExploreScreen() {
|
|
||||||
const { db } = useStorage();
|
|
||||||
const { getContactsByPubkey } = useNostr();
|
|
||||||
const { project } = useReactFlow();
|
|
||||||
|
|
||||||
const defaultContacts = useMemo(() => getMultipleRandom(db.account.contacts, 10), []);
|
|
||||||
const reactFlowWrapper = useRef(null);
|
|
||||||
const connectingNodeId = useRef(null);
|
|
||||||
|
|
||||||
const initialNodes = [
|
|
||||||
{
|
|
||||||
id: '0',
|
|
||||||
type: 'user',
|
|
||||||
position: { x: 141, y: 0 },
|
|
||||||
data: { list: [], title: '', pubkey: db.account.pubkey },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
type: 'userGroup',
|
|
||||||
position: { x: 0, y: 200 },
|
|
||||||
data: { list: defaultContacts, title: 'Starting Point', pubkey: '' },
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const initialEdges = [{ id: 'e0-1', type: 'buttonedge', source: '0', target: '1' }];
|
|
||||||
|
|
||||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
|
||||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
|
||||||
|
|
||||||
const onConnect = useCallback((params) => setEdges((eds) => addEdge(params, eds)), []);
|
|
||||||
|
|
||||||
const onConnectStart = useCallback((_, { nodeId }) => {
|
|
||||||
connectingNodeId.current = nodeId;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const onConnectEnd = useCallback(
|
|
||||||
async (event) => {
|
|
||||||
const targetIsPane = event.target.classList.contains('react-flow__pane');
|
|
||||||
|
|
||||||
if (targetIsPane) {
|
|
||||||
const { top, left } = reactFlowWrapper.current.getBoundingClientRect();
|
|
||||||
|
|
||||||
const id = getId();
|
|
||||||
const prevData = nodes.slice(-1)[0];
|
|
||||||
const randomPubkey = getMultipleRandom(prevData.data.list, 1)[0];
|
|
||||||
|
|
||||||
const newContactList = await getContactsByPubkey(randomPubkey);
|
|
||||||
const newNode = {
|
|
||||||
id,
|
|
||||||
type: 'userGroup',
|
|
||||||
position: project({ x: event.clientX - left, y: event.clientY - top }),
|
|
||||||
data: { list: newContactList, title: null, pubkey: randomPubkey },
|
|
||||||
};
|
|
||||||
|
|
||||||
setNodes((nds) => nds.concat(newNode));
|
|
||||||
setEdges((eds) =>
|
|
||||||
eds.concat({
|
|
||||||
id,
|
|
||||||
type: 'buttonedge',
|
|
||||||
source: connectingNodeId.current,
|
|
||||||
target: id,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[project]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="h-full w-full" ref={reactFlowWrapper}>
|
|
||||||
<ReactFlow
|
|
||||||
proOptions={{ hideAttribution: true }}
|
|
||||||
nodes={nodes}
|
|
||||||
edges={edges}
|
|
||||||
nodeTypes={nodeTypes}
|
|
||||||
edgeTypes={edgeTypes}
|
|
||||||
connectionLineComponent={Line}
|
|
||||||
onNodesChange={onNodesChange}
|
|
||||||
onEdgesChange={onEdgesChange}
|
|
||||||
onConnect={onConnect}
|
|
||||||
onConnectStart={onConnectStart}
|
|
||||||
onConnectEnd={onConnectEnd}
|
|
||||||
connectionMode={ConnectionMode.Loose}
|
|
||||||
minZoom={0.8}
|
|
||||||
maxZoom={1.2}
|
|
||||||
fitView
|
|
||||||
>
|
|
||||||
<Background color="#3f3f46" />
|
|
||||||
</ReactFlow>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -50,9 +50,15 @@ export class Ark {
|
|||||||
hashtag: boolean;
|
hashtag: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor({ storage }: { storage: Database }) {
|
constructor({ storage, platform }: { storage: Database; platform: Platform }) {
|
||||||
this.#storage = storage;
|
this.#storage = storage;
|
||||||
this.#init();
|
this.platform = platform;
|
||||||
|
this.settings = {
|
||||||
|
autoupdate: false,
|
||||||
|
outbox: false,
|
||||||
|
media: true,
|
||||||
|
hashtag: true,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async #keyring_save(key: string, value: string) {
|
async #keyring_save(key: string, value: string) {
|
||||||
@ -74,10 +80,8 @@ export class Ark {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async #initNostrSigner({ nsecbunker }: { nsecbunker?: boolean }) {
|
async #initNostrSigner({ nsecbunker }: { nsecbunker?: boolean }) {
|
||||||
if (!this.account) {
|
const account = await this.getActiveAccount();
|
||||||
this.readyToSign = false;
|
this.account = account;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// NIP-46 Signer
|
// NIP-46 Signer
|
||||||
@ -128,7 +132,7 @@ export class Ark {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async #init() {
|
public async init() {
|
||||||
const outboxSetting = await this.getSettingValue('outbox');
|
const outboxSetting = await this.getSettingValue('outbox');
|
||||||
const bunkerSetting = await this.getSettingValue('nsecbunker');
|
const bunkerSetting = await this.getSettingValue('nsecbunker');
|
||||||
|
|
||||||
@ -178,6 +182,7 @@ export class Ark {
|
|||||||
this.account.contacts = [...contacts].map((user) => user.pubkey);
|
this.account.contacts = [...contacts].map((user) => user.pubkey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.relays = [...ndk.pool.relays.values()].map((relay) => relay.url);
|
||||||
this.#ndk = ndk;
|
this.#ndk = ndk;
|
||||||
this.#fetcher = fetcher;
|
this.#fetcher = fetcher;
|
||||||
}
|
}
|
||||||
@ -214,10 +219,8 @@ export class Ark {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (results.length) {
|
if (results.length) {
|
||||||
this.account = results[0];
|
return results[0];
|
||||||
this.account.contacts = [];
|
|
||||||
} else {
|
} else {
|
||||||
console.log('no active account, please create new account');
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -766,6 +769,72 @@ export class Ark {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all NIP-04 messages
|
||||||
|
* @deprecated NIP-04 will be replace by NIP-44 in the next update
|
||||||
|
*/
|
||||||
|
public async getAllChats() {
|
||||||
|
const events = await this.#fetcher.fetchAllEvents(
|
||||||
|
this.relays,
|
||||||
|
{
|
||||||
|
kinds: [NDKKind.EncryptedDirectMessage],
|
||||||
|
'#p': [this.account.pubkey],
|
||||||
|
},
|
||||||
|
{ since: 0 }
|
||||||
|
);
|
||||||
|
|
||||||
|
const dedup: NDKEvent[] = Object.values(
|
||||||
|
events.reduce((ev, { id, content, pubkey, created_at, tags }) => {
|
||||||
|
if (ev[pubkey]) {
|
||||||
|
if (ev[pubkey].created_at < created_at) {
|
||||||
|
ev[pubkey] = { id, content, pubkey, created_at, tags };
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ev[pubkey] = { id, content, pubkey, created_at, tags };
|
||||||
|
}
|
||||||
|
return ev;
|
||||||
|
}, {})
|
||||||
|
);
|
||||||
|
|
||||||
|
return dedup;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all NIP-04 messages by pubkey
|
||||||
|
* @deprecated NIP-04 will be replace by NIP-44 in the next update
|
||||||
|
*/
|
||||||
|
public async getAllMessagesByPubkey({ pubkey }: { pubkey: string }) {
|
||||||
|
let senderMessages: NostrEventExt<false>[] = [];
|
||||||
|
|
||||||
|
if (pubkey !== this.account.pubkey) {
|
||||||
|
senderMessages = await this.#fetcher.fetchAllEvents(
|
||||||
|
this.relays,
|
||||||
|
{
|
||||||
|
kinds: [NDKKind.EncryptedDirectMessage],
|
||||||
|
authors: [pubkey],
|
||||||
|
'#p': [this.account.pubkey],
|
||||||
|
},
|
||||||
|
{ since: 0 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const userMessages = await this.#fetcher.fetchAllEvents(
|
||||||
|
this.relays,
|
||||||
|
{
|
||||||
|
kinds: [NDKKind.EncryptedDirectMessage],
|
||||||
|
authors: [this.account.pubkey],
|
||||||
|
'#p': [pubkey],
|
||||||
|
},
|
||||||
|
{ since: 0 }
|
||||||
|
);
|
||||||
|
|
||||||
|
const all = [...senderMessages, ...userMessages].sort(
|
||||||
|
(a, b) => a.created_at - b.created_at
|
||||||
|
);
|
||||||
|
|
||||||
|
return all as unknown as NDKEvent[];
|
||||||
|
}
|
||||||
|
|
||||||
public async nip04Decrypt({ event }: { event: NDKEvent }) {
|
public async nip04Decrypt({ event }: { event: NDKEvent }) {
|
||||||
try {
|
try {
|
||||||
const sender = new NDKUser({
|
const sender = new NDKUser({
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { ask } from '@tauri-apps/plugin-dialog';
|
import { ask } from '@tauri-apps/plugin-dialog';
|
||||||
|
import { platform } from '@tauri-apps/plugin-os';
|
||||||
import { relaunch } from '@tauri-apps/plugin-process';
|
import { relaunch } from '@tauri-apps/plugin-process';
|
||||||
import Database from '@tauri-apps/plugin-sql';
|
import Database from '@tauri-apps/plugin-sql';
|
||||||
import { check } from '@tauri-apps/plugin-updater';
|
import { check } from '@tauri-apps/plugin-updater';
|
||||||
@ -26,9 +27,10 @@ const ArkProvider = ({ children }: PropsWithChildren<object>) => {
|
|||||||
async function initArk() {
|
async function initArk() {
|
||||||
try {
|
try {
|
||||||
const sqlite = await Database.load('sqlite:lume_v2.db');
|
const sqlite = await Database.load('sqlite:lume_v2.db');
|
||||||
const _ark = new Ark({ storage: sqlite });
|
const platformName = await platform();
|
||||||
|
|
||||||
if (!_ark.account) await _ark.getActiveAccount();
|
const _ark = new Ark({ storage: sqlite, platform: platformName });
|
||||||
|
await _ark.init();
|
||||||
|
|
||||||
const settings = await _ark.getAllSettings();
|
const settings = await _ark.getAllSettings();
|
||||||
let autoUpdater = false;
|
let autoUpdater = false;
|
||||||
|
@ -1,426 +0,0 @@
|
|||||||
// inspired by: https://github.com/nostr-dev-kit/ndk/tree/master/ndk-cache-dexie
|
|
||||||
import { NDKEvent, NDKRelay, profileFromEvent } from '@nostr-dev-kit/ndk';
|
|
||||||
import type {
|
|
||||||
Hexpubkey,
|
|
||||||
NDKCacheAdapter,
|
|
||||||
NDKFilter,
|
|
||||||
NDKSubscription,
|
|
||||||
NDKUserProfile,
|
|
||||||
NostrEvent,
|
|
||||||
} from '@nostr-dev-kit/ndk';
|
|
||||||
import { LRUCache } from 'lru-cache';
|
|
||||||
import { matchFilter } from 'nostr-tools';
|
|
||||||
|
|
||||||
import { LumeStorage } from '@libs/storage/instance';
|
|
||||||
|
|
||||||
export default class NDKCacheAdapterTauri implements NDKCacheAdapter {
|
|
||||||
public db: LumeStorage;
|
|
||||||
public profiles?: LRUCache<Hexpubkey, NDKUserProfile>;
|
|
||||||
private dirtyProfiles: Set<Hexpubkey> = new Set();
|
|
||||||
readonly locking: boolean;
|
|
||||||
|
|
||||||
constructor(db: LumeStorage) {
|
|
||||||
this.db = db;
|
|
||||||
this.locking = true;
|
|
||||||
|
|
||||||
this.profiles = new LRUCache({
|
|
||||||
max: 100000,
|
|
||||||
});
|
|
||||||
|
|
||||||
setInterval(() => {
|
|
||||||
this.dumpProfiles();
|
|
||||||
}, 1000 * 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async query(subscription: NDKSubscription): Promise<void> {
|
|
||||||
Promise.allSettled(
|
|
||||||
subscription.filters.map((filter) => this.processFilter(filter, subscription))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async fetchProfile(pubkey: Hexpubkey) {
|
|
||||||
if (!this.profiles) return null;
|
|
||||||
|
|
||||||
let profile = this.profiles.get(pubkey);
|
|
||||||
|
|
||||||
if (!profile) {
|
|
||||||
const user = await this.db.getCacheUser(pubkey);
|
|
||||||
if (user) {
|
|
||||||
profile = user.profile as NDKUserProfile;
|
|
||||||
this.profiles.set(pubkey, profile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return profile;
|
|
||||||
}
|
|
||||||
|
|
||||||
public saveProfile(pubkey: Hexpubkey, profile: NDKUserProfile) {
|
|
||||||
if (!this.profiles) return;
|
|
||||||
|
|
||||||
this.profiles.set(pubkey, profile);
|
|
||||||
|
|
||||||
this.dirtyProfiles.add(pubkey);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async processFilter(
|
|
||||||
filter: NDKFilter,
|
|
||||||
subscription: NDKSubscription
|
|
||||||
): Promise<void> {
|
|
||||||
const _filter = { ...filter };
|
|
||||||
delete _filter.limit;
|
|
||||||
const filterKeys = Object.keys(_filter || {}).sort();
|
|
||||||
|
|
||||||
try {
|
|
||||||
(await this.byKindAndAuthor(filterKeys, filter, subscription)) ||
|
|
||||||
(await this.byAuthors(filterKeys, filter, subscription)) ||
|
|
||||||
(await this.byKinds(filterKeys, filter, subscription)) ||
|
|
||||||
(await this.byIdsQuery(filterKeys, filter, subscription)) ||
|
|
||||||
(await this.byNip33Query(filterKeys, filter, subscription)) ||
|
|
||||||
(await this.byTagsAndOptionallyKinds(filterKeys, filter, subscription));
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async setEvent(
|
|
||||||
event: NDKEvent,
|
|
||||||
_filter: NDKFilter,
|
|
||||||
relay?: NDKRelay
|
|
||||||
): Promise<void> {
|
|
||||||
if (event.kind === 0) {
|
|
||||||
if (!this.profiles) return;
|
|
||||||
|
|
||||||
const profile: NDKUserProfile = profileFromEvent(event);
|
|
||||||
this.profiles.set(event.pubkey, profile);
|
|
||||||
} else {
|
|
||||||
let addEvent = true;
|
|
||||||
|
|
||||||
if (event.isParamReplaceable()) {
|
|
||||||
const replaceableId = `${event.kind}:${event.pubkey}:${event.tagId()}`;
|
|
||||||
const existingEvent = await this.db.getCacheEvent(replaceableId);
|
|
||||||
if (
|
|
||||||
existingEvent &&
|
|
||||||
event.created_at &&
|
|
||||||
existingEvent.createdAt > event.created_at
|
|
||||||
) {
|
|
||||||
addEvent = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (addEvent) {
|
|
||||||
this.db.setCacheEvent({
|
|
||||||
id: event.tagId(),
|
|
||||||
pubkey: event.pubkey,
|
|
||||||
content: event.content,
|
|
||||||
kind: event.kind!,
|
|
||||||
createdAt: event.created_at!,
|
|
||||||
relay: relay?.url,
|
|
||||||
event: JSON.stringify(event.rawEvent()),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Don't cache contact lists as tags since it's expensive
|
|
||||||
// and there is no use case for it
|
|
||||||
if (event.kind !== 3) {
|
|
||||||
event.tags.forEach((tag) => {
|
|
||||||
if (tag[0].length !== 1) return;
|
|
||||||
|
|
||||||
this.db.setCacheEventTag({
|
|
||||||
id: `${event.id}:${tag[0]}:${tag[1]}`,
|
|
||||||
eventId: event.id,
|
|
||||||
tag: tag[0],
|
|
||||||
value: tag[1],
|
|
||||||
tagValue: tag[0] + tag[1],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Searches by authors
|
|
||||||
*/
|
|
||||||
private async byAuthors(
|
|
||||||
filterKeys: string[],
|
|
||||||
filter: NDKFilter,
|
|
||||||
subscription: NDKSubscription
|
|
||||||
): Promise<boolean> {
|
|
||||||
const f = ['authors'];
|
|
||||||
const hasAllKeys =
|
|
||||||
filterKeys.length === f.length && f.every((k) => filterKeys.includes(k));
|
|
||||||
|
|
||||||
let foundEvents = false;
|
|
||||||
|
|
||||||
if (hasAllKeys && filter.authors) {
|
|
||||||
for (const pubkey of filter.authors) {
|
|
||||||
const events = await this.db.getCacheEventsByPubkey(pubkey);
|
|
||||||
for (const event of events) {
|
|
||||||
let rawEvent: NostrEvent;
|
|
||||||
try {
|
|
||||||
rawEvent = JSON.parse(event.event);
|
|
||||||
} catch (e) {
|
|
||||||
console.log('failed to parse event', e);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ndkEvent = new NDKEvent(undefined, rawEvent);
|
|
||||||
const relay = event.relay ? new NDKRelay(event.relay) : undefined;
|
|
||||||
subscription.eventReceived(ndkEvent, relay, true);
|
|
||||||
foundEvents = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return foundEvents;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Searches by kinds
|
|
||||||
*/
|
|
||||||
private async byKinds(
|
|
||||||
filterKeys: string[],
|
|
||||||
filter: NDKFilter,
|
|
||||||
subscription: NDKSubscription
|
|
||||||
): Promise<boolean> {
|
|
||||||
const f = ['kinds'];
|
|
||||||
const hasAllKeys =
|
|
||||||
filterKeys.length === f.length && f.every((k) => filterKeys.includes(k));
|
|
||||||
|
|
||||||
let foundEvents = false;
|
|
||||||
|
|
||||||
if (hasAllKeys && filter.kinds) {
|
|
||||||
for (const kind of filter.kinds) {
|
|
||||||
const events = await this.db.getCacheEventsByKind(kind);
|
|
||||||
for (const event of events) {
|
|
||||||
let rawEvent: NostrEvent;
|
|
||||||
try {
|
|
||||||
rawEvent = JSON.parse(event.event);
|
|
||||||
} catch (e) {
|
|
||||||
console.log('failed to parse event', e);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ndkEvent = new NDKEvent(undefined, rawEvent);
|
|
||||||
const relay = event.relay ? new NDKRelay(event.relay) : undefined;
|
|
||||||
subscription.eventReceived(ndkEvent, relay, true);
|
|
||||||
foundEvents = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return foundEvents;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Searches by ids
|
|
||||||
*/
|
|
||||||
private async byIdsQuery(
|
|
||||||
filterKeys: string[],
|
|
||||||
filter: NDKFilter,
|
|
||||||
subscription: NDKSubscription
|
|
||||||
): Promise<boolean> {
|
|
||||||
const f = ['ids'];
|
|
||||||
const hasAllKeys =
|
|
||||||
filterKeys.length === f.length && f.every((k) => filterKeys.includes(k));
|
|
||||||
|
|
||||||
if (hasAllKeys && filter.ids) {
|
|
||||||
for (const id of filter.ids) {
|
|
||||||
const event = await this.db.getCacheEvent(id);
|
|
||||||
if (!event) continue;
|
|
||||||
|
|
||||||
let rawEvent: NostrEvent;
|
|
||||||
try {
|
|
||||||
rawEvent = JSON.parse(event.event);
|
|
||||||
} catch (e) {
|
|
||||||
console.log('failed to parse event', e);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ndkEvent = new NDKEvent(undefined, rawEvent);
|
|
||||||
const relay = event.relay ? new NDKRelay(event.relay) : undefined;
|
|
||||||
subscription.eventReceived(ndkEvent, relay, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Searches by NIP-33
|
|
||||||
*/
|
|
||||||
private async byNip33Query(
|
|
||||||
filterKeys: string[],
|
|
||||||
filter: NDKFilter,
|
|
||||||
subscription: NDKSubscription
|
|
||||||
): Promise<boolean> {
|
|
||||||
const f = ['#d', 'authors', 'kinds'];
|
|
||||||
const hasAllKeys =
|
|
||||||
filterKeys.length === f.length && f.every((k) => filterKeys.includes(k));
|
|
||||||
|
|
||||||
if (hasAllKeys && filter.kinds && filter.authors) {
|
|
||||||
for (const kind of filter.kinds) {
|
|
||||||
const replaceableKind = kind >= 30000 && kind < 40000;
|
|
||||||
|
|
||||||
if (!replaceableKind) continue;
|
|
||||||
|
|
||||||
for (const author of filter.authors) {
|
|
||||||
for (const dTag of filter['#d']) {
|
|
||||||
const replaceableId = `${kind}:${author}:${dTag}`;
|
|
||||||
const event = await this.db.getCacheEvent(replaceableId);
|
|
||||||
if (!event) continue;
|
|
||||||
|
|
||||||
let rawEvent: NostrEvent;
|
|
||||||
try {
|
|
||||||
rawEvent = JSON.parse(event.event);
|
|
||||||
} catch (e) {
|
|
||||||
console.log('failed to parse event', e);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ndkEvent = new NDKEvent(undefined, rawEvent);
|
|
||||||
const relay = event.relay ? new NDKRelay(event.relay) : undefined;
|
|
||||||
subscription.eventReceived(ndkEvent, relay, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Searches by kind & author
|
|
||||||
*/
|
|
||||||
private async byKindAndAuthor(
|
|
||||||
filterKeys: string[],
|
|
||||||
filter: NDKFilter,
|
|
||||||
subscription: NDKSubscription
|
|
||||||
): Promise<boolean> {
|
|
||||||
const f = ['authors', 'kinds'];
|
|
||||||
const hasAllKeys =
|
|
||||||
filterKeys.length === f.length && f.every((k) => filterKeys.includes(k));
|
|
||||||
let foundEvents = false;
|
|
||||||
|
|
||||||
if (!hasAllKeys) return false;
|
|
||||||
|
|
||||||
if (filter.kinds && filter.authors) {
|
|
||||||
for (const kind of filter.kinds) {
|
|
||||||
for (const author of filter.authors) {
|
|
||||||
const events = await this.db.getCacheEventsByKindAndAuthor(kind, author);
|
|
||||||
|
|
||||||
for (const event of events) {
|
|
||||||
let rawEvent: NostrEvent;
|
|
||||||
try {
|
|
||||||
rawEvent = JSON.parse(event.event);
|
|
||||||
} catch (e) {
|
|
||||||
console.log('failed to parse event', e);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ndkEvent = new NDKEvent(undefined, rawEvent);
|
|
||||||
const relay = event.relay ? new NDKRelay(event.relay) : undefined;
|
|
||||||
subscription.eventReceived(ndkEvent, relay, true);
|
|
||||||
foundEvents = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return foundEvents;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Searches by tags and optionally filters by tags
|
|
||||||
*/
|
|
||||||
private async byTagsAndOptionallyKinds(
|
|
||||||
filterKeys: string[],
|
|
||||||
filter: NDKFilter,
|
|
||||||
subscription: NDKSubscription
|
|
||||||
): Promise<boolean> {
|
|
||||||
for (const filterKey of filterKeys) {
|
|
||||||
const isKind = filterKey === 'kinds';
|
|
||||||
const isTag = filterKey.startsWith('#') && filterKey.length === 2;
|
|
||||||
|
|
||||||
if (!isKind && !isTag) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const events = await this.filterByTag(filterKeys, filter);
|
|
||||||
const kinds = filter.kinds as number[];
|
|
||||||
|
|
||||||
for (const event of events) {
|
|
||||||
if (!kinds?.includes(event.kind!)) continue;
|
|
||||||
|
|
||||||
subscription.eventReceived(event, undefined, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async filterByTag(
|
|
||||||
filterKeys: string[],
|
|
||||||
filter: NDKFilter
|
|
||||||
): Promise<NDKEvent[]> {
|
|
||||||
const retEvents: NDKEvent[] = [];
|
|
||||||
|
|
||||||
for (const filterKey of filterKeys) {
|
|
||||||
if (filterKey.length !== 2) continue;
|
|
||||||
const tag = filterKey.slice(1);
|
|
||||||
// const values = filter[filterKey] as string[];
|
|
||||||
const values: string[] = [];
|
|
||||||
for (const [key, value] of Object.entries(filter)) {
|
|
||||||
if (key === filterKey) values.push(value as string);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const value of values) {
|
|
||||||
const eventTags = await this.db.getCacheEventTagsByTagValue(tag + value);
|
|
||||||
if (!eventTags.length) continue;
|
|
||||||
|
|
||||||
const eventIds = eventTags.map((t) => t.eventId);
|
|
||||||
|
|
||||||
const events = await this.db.getCacheEvents(eventIds);
|
|
||||||
for (const event of events) {
|
|
||||||
let rawEvent;
|
|
||||||
try {
|
|
||||||
rawEvent = JSON.parse(event.event);
|
|
||||||
|
|
||||||
// Make sure all passed filters match the event
|
|
||||||
if (!matchFilter(filter, rawEvent)) continue;
|
|
||||||
} catch (e) {
|
|
||||||
console.log('failed to parse event', e);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ndkEvent = new NDKEvent(undefined, rawEvent);
|
|
||||||
const relay = event.relay ? new NDKRelay(event.relay) : undefined;
|
|
||||||
ndkEvent.relay = relay;
|
|
||||||
retEvents.push(ndkEvent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return retEvents;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async dumpProfiles(): Promise<void> {
|
|
||||||
const profiles = [];
|
|
||||||
|
|
||||||
if (!this.profiles) return;
|
|
||||||
|
|
||||||
for (const pubkey of this.dirtyProfiles) {
|
|
||||||
const profile = this.profiles.get(pubkey);
|
|
||||||
|
|
||||||
if (!profile) continue;
|
|
||||||
|
|
||||||
profiles.push({
|
|
||||||
pubkey,
|
|
||||||
profile: JSON.stringify(profile),
|
|
||||||
createdAt: Date.now(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (profiles.length) {
|
|
||||||
await this.db.setCacheProfiles(profiles);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.dirtyProfiles.clear();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,214 +0,0 @@
|
|||||||
import NDK, {
|
|
||||||
NDKEvent,
|
|
||||||
NDKKind,
|
|
||||||
NDKNip46Signer,
|
|
||||||
NDKPrivateKeySigner,
|
|
||||||
} from '@nostr-dev-kit/ndk';
|
|
||||||
import { ndkAdapter } from '@nostr-fetch/adapter-ndk';
|
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
|
||||||
import { ask } from '@tauri-apps/plugin-dialog';
|
|
||||||
import { relaunch } from '@tauri-apps/plugin-process';
|
|
||||||
import { NostrFetcher, normalizeRelayUrlSet } from 'nostr-fetch';
|
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { toast } from 'sonner';
|
|
||||||
|
|
||||||
import NDKCacheAdapterTauri from '@libs/ndk/cache';
|
|
||||||
import { useStorage } from '@libs/storage/provider';
|
|
||||||
|
|
||||||
import { FETCH_LIMIT } from '@utils/constants';
|
|
||||||
|
|
||||||
export const NDKInstance = () => {
|
|
||||||
const { db } = useStorage();
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
|
|
||||||
const [ndk, setNDK] = useState<NDK | undefined>(undefined);
|
|
||||||
const [fetcher, setFetcher] = useState<NostrFetcher | undefined>(undefined);
|
|
||||||
const [relayUrls, setRelayUrls] = useState<string[]>([]);
|
|
||||||
|
|
||||||
async function getSigner(nsecbunker?: boolean) {
|
|
||||||
if (!db.account) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// NIP-46 Signer
|
|
||||||
if (nsecbunker) {
|
|
||||||
const localSignerPrivkey = await db.secureLoad(`${db.account.id}-nsecbunker`);
|
|
||||||
if (!localSignerPrivkey) return null;
|
|
||||||
|
|
||||||
const localSigner = new NDKPrivateKeySigner(localSignerPrivkey);
|
|
||||||
const bunker = new NDK({
|
|
||||||
explicitRelayUrls: ['wss://relay.nsecbunker.com', 'wss://nostr.vulpem.com'],
|
|
||||||
});
|
|
||||||
await bunker.connect();
|
|
||||||
|
|
||||||
const remoteSigner = new NDKNip46Signer(bunker, db.account.pubkey, localSigner);
|
|
||||||
await remoteSigner.blockUntilReady();
|
|
||||||
|
|
||||||
return remoteSigner;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Privkey Signer
|
|
||||||
const userPrivkey = await db.secureLoad(db.account.pubkey);
|
|
||||||
if (!userPrivkey) return null;
|
|
||||||
return new NDKPrivateKeySigner(userPrivkey);
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
if (e === 'Token already redeemed') {
|
|
||||||
toast.info(
|
|
||||||
'nsecbunker token already redeemed. You need to re-login with another token.'
|
|
||||||
);
|
|
||||||
|
|
||||||
await db.secureRemove(`${db.account.pubkey}-nsecbunker`);
|
|
||||||
await db.accountLogout();
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function initNDK() {
|
|
||||||
const outboxSetting = await db.getSettingValue('outbox');
|
|
||||||
const bunkerSetting = await db.getSettingValue('nsecbunker');
|
|
||||||
|
|
||||||
const bunker = !!parseInt(bunkerSetting);
|
|
||||||
const outbox = !!parseInt(outboxSetting);
|
|
||||||
|
|
||||||
const explicitRelayUrls = normalizeRelayUrlSet([
|
|
||||||
'wss://relay.damus.io',
|
|
||||||
'wss://relay.nostr.band',
|
|
||||||
'wss://nos.lol',
|
|
||||||
'wss://nostr.mutinywallet.com',
|
|
||||||
]);
|
|
||||||
|
|
||||||
// #TODO: user should config outbox relays
|
|
||||||
const outboxRelayUrls = normalizeRelayUrlSet(['wss://purplepag.es']);
|
|
||||||
|
|
||||||
// #TODO: user should config blacklist relays
|
|
||||||
const blacklistRelayUrls = normalizeRelayUrlSet(['wss://brb.io']);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const tauriAdapter = new NDKCacheAdapterTauri(db);
|
|
||||||
const instance = new NDK({
|
|
||||||
explicitRelayUrls,
|
|
||||||
outboxRelayUrls,
|
|
||||||
blacklistRelayUrls,
|
|
||||||
enableOutboxModel: outbox,
|
|
||||||
autoConnectUserRelays: true,
|
|
||||||
autoFetchUserMutelist: true,
|
|
||||||
cacheAdapter: tauriAdapter,
|
|
||||||
// clientName: 'Lume',
|
|
||||||
// clientNip89: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
// add signer if exist
|
|
||||||
const signer = await getSigner(bunker);
|
|
||||||
if (signer) instance.signer = signer;
|
|
||||||
|
|
||||||
// connect
|
|
||||||
await instance.connect();
|
|
||||||
const _fetcher = NostrFetcher.withCustomPool(ndkAdapter(instance));
|
|
||||||
|
|
||||||
// update account's metadata
|
|
||||||
if (db.account) {
|
|
||||||
const user = instance.getUser({ pubkey: db.account.pubkey });
|
|
||||||
instance.activeUser = user;
|
|
||||||
|
|
||||||
const contacts = await user.follows(undefined /* outbox */);
|
|
||||||
db.account.contacts = [...contacts].map((user) => user.pubkey);
|
|
||||||
|
|
||||||
// prefetch newsfeed
|
|
||||||
await queryClient.prefetchInfiniteQuery({
|
|
||||||
queryKey: ['newsfeed'],
|
|
||||||
initialPageParam: 0,
|
|
||||||
queryFn: async ({
|
|
||||||
signal,
|
|
||||||
pageParam,
|
|
||||||
}: {
|
|
||||||
signal: AbortSignal;
|
|
||||||
pageParam: number;
|
|
||||||
}) => {
|
|
||||||
const rootIds = new Set();
|
|
||||||
const dedupQueue = new Set();
|
|
||||||
|
|
||||||
const events = await _fetcher.fetchLatestEvents(
|
|
||||||
explicitRelayUrls,
|
|
||||||
{
|
|
||||||
kinds: [NDKKind.Text, NDKKind.Repost],
|
|
||||||
authors: db.account.contacts,
|
|
||||||
},
|
|
||||||
FETCH_LIMIT,
|
|
||||||
{ asOf: pageParam === 0 ? undefined : pageParam, abortSignal: signal }
|
|
||||||
);
|
|
||||||
|
|
||||||
const ndkEvents = events.map((event) => {
|
|
||||||
return new NDKEvent(ndk, event);
|
|
||||||
});
|
|
||||||
|
|
||||||
ndkEvents.forEach((event) => {
|
|
||||||
const tags = event.tags.filter((el) => el[0] === 'e');
|
|
||||||
if (tags && tags.length > 0) {
|
|
||||||
const rootId = tags.filter((el) => el[3] === 'root')[1] ?? tags[0][1];
|
|
||||||
if (rootIds.has(rootId)) return dedupQueue.add(event.id);
|
|
||||||
rootIds.add(rootId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return ndkEvents
|
|
||||||
.filter((event) => !dedupQueue.has(event.id))
|
|
||||||
.sort((a, b) => b.created_at - a.created_at);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// prefetch notification
|
|
||||||
await queryClient.prefetchInfiniteQuery({
|
|
||||||
queryKey: ['notification'],
|
|
||||||
initialPageParam: 0,
|
|
||||||
queryFn: async ({
|
|
||||||
signal,
|
|
||||||
pageParam,
|
|
||||||
}: {
|
|
||||||
signal: AbortSignal;
|
|
||||||
pageParam: number;
|
|
||||||
}) => {
|
|
||||||
const events = await _fetcher.fetchLatestEvents(
|
|
||||||
explicitRelayUrls,
|
|
||||||
{
|
|
||||||
kinds: [NDKKind.Text, NDKKind.Repost, NDKKind.Reaction, NDKKind.Zap],
|
|
||||||
'#p': [db.account.pubkey],
|
|
||||||
},
|
|
||||||
FETCH_LIMIT,
|
|
||||||
{ asOf: pageParam === 0 ? undefined : pageParam, abortSignal: signal }
|
|
||||||
);
|
|
||||||
|
|
||||||
const ndkEvents = events.map((event) => {
|
|
||||||
return new NDKEvent(ndk, event);
|
|
||||||
});
|
|
||||||
|
|
||||||
return ndkEvents.sort((a, b) => b.created_at - a.created_at);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setNDK(instance);
|
|
||||||
setFetcher(_fetcher);
|
|
||||||
setRelayUrls(explicitRelayUrls);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
const yes = await ask(e, {
|
|
||||||
title: 'Lume',
|
|
||||||
type: 'error',
|
|
||||||
okLabel: 'Yes',
|
|
||||||
});
|
|
||||||
if (yes) relaunch();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!ndk) initNDK();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return {
|
|
||||||
ndk,
|
|
||||||
fetcher,
|
|
||||||
relayUrls,
|
|
||||||
};
|
|
||||||
};
|
|
@ -1,80 +0,0 @@
|
|||||||
// source: https://github.com/nostr-dev-kit/ndk-react/
|
|
||||||
import NDK from '@nostr-dev-kit/ndk';
|
|
||||||
import Markdown from 'markdown-to-jsx';
|
|
||||||
import { NostrFetcher } from 'nostr-fetch';
|
|
||||||
import { PropsWithChildren, createContext, useContext } from 'react';
|
|
||||||
|
|
||||||
import { NDKInstance } from '@libs/ndk/instance';
|
|
||||||
|
|
||||||
import { LoaderIcon } from '@shared/icons';
|
|
||||||
|
|
||||||
import { QUOTES } from '@utils/constants';
|
|
||||||
|
|
||||||
interface NDKContext {
|
|
||||||
ndk: undefined | NDK;
|
|
||||||
fetcher: undefined | NostrFetcher;
|
|
||||||
relayUrls: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const NDKContext = createContext<NDKContext>({
|
|
||||||
ndk: undefined,
|
|
||||||
fetcher: undefined,
|
|
||||||
relayUrls: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
const NDKProvider = ({ children }: PropsWithChildren<object>) => {
|
|
||||||
const { ndk, relayUrls, fetcher } = NDKInstance();
|
|
||||||
|
|
||||||
if (!ndk)
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-tauri-drag-region
|
|
||||||
className="relative flex h-screen w-screen items-center justify-center bg-neutral-50 dark:bg-neutral-950"
|
|
||||||
>
|
|
||||||
<div className="flex max-w-2xl flex-col items-start gap-1">
|
|
||||||
<h5 className="font-semibold uppercase">TIP:</h5>
|
|
||||||
<Markdown
|
|
||||||
options={{
|
|
||||||
overrides: {
|
|
||||||
a: {
|
|
||||||
props: {
|
|
||||||
className: 'text-blue-500 hover:text-blue-600',
|
|
||||||
target: '_blank',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
className="text-4xl font-semibold leading-snug text-neutral-300 dark:text-neutral-700"
|
|
||||||
>
|
|
||||||
{QUOTES[Math.floor(Math.random() * QUOTES.length)]}
|
|
||||||
</Markdown>
|
|
||||||
</div>
|
|
||||||
<div className="absolute bottom-5 right-5 inline-flex items-center gap-2.5">
|
|
||||||
<LoaderIcon className="h-6 w-6 animate-spin text-blue-500" />
|
|
||||||
<p className="font-semibold">Connecting to relays...</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<NDKContext.Provider
|
|
||||||
value={{
|
|
||||||
ndk,
|
|
||||||
relayUrls,
|
|
||||||
fetcher,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</NDKContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const useNDK = () => {
|
|
||||||
const context = useContext(NDKContext);
|
|
||||||
if (context === undefined) {
|
|
||||||
throw new Error('import NDKProvider to use useNDK');
|
|
||||||
}
|
|
||||||
return context;
|
|
||||||
};
|
|
||||||
|
|
||||||
export { NDKProvider, useNDK };
|
|
@ -1,486 +0,0 @@
|
|||||||
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
|
||||||
import { invoke } from '@tauri-apps/api/primitives';
|
|
||||||
import { Platform } from '@tauri-apps/plugin-os';
|
|
||||||
import Database from '@tauri-apps/plugin-sql';
|
|
||||||
|
|
||||||
import { rawEvent } from '@utils/transform';
|
|
||||||
import type {
|
|
||||||
Account,
|
|
||||||
DBEvent,
|
|
||||||
NDKCacheEvent,
|
|
||||||
NDKCacheEventTag,
|
|
||||||
NDKCacheUser,
|
|
||||||
NDKCacheUserProfile,
|
|
||||||
Relays,
|
|
||||||
Widget,
|
|
||||||
} from '@utils/types';
|
|
||||||
|
|
||||||
export class LumeStorage {
|
|
||||||
public db: Database;
|
|
||||||
public account: Account | null;
|
|
||||||
public platform: Platform | null;
|
|
||||||
public settings: {
|
|
||||||
autoupdate: boolean;
|
|
||||||
outbox: boolean;
|
|
||||||
media: boolean;
|
|
||||||
hashtag: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(sqlite: Database, platform: Platform) {
|
|
||||||
this.db = sqlite;
|
|
||||||
this.account = null;
|
|
||||||
this.platform = platform;
|
|
||||||
this.settings = { autoupdate: false, outbox: false, media: true, hashtag: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
public async secureSave(key: string, value: string) {
|
|
||||||
return await invoke('secure_save', { key, value });
|
|
||||||
}
|
|
||||||
|
|
||||||
public async secureLoad(key: string) {
|
|
||||||
try {
|
|
||||||
const value: string = await invoke('secure_load', { key });
|
|
||||||
if (!value) return null;
|
|
||||||
return value;
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async secureRemove(key: string) {
|
|
||||||
return await invoke('secure_remove', { key });
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getAllCacheUsers() {
|
|
||||||
const results: Array<NDKCacheUser> = await this.db.select(
|
|
||||||
'SELECT * FROM ndk_users ORDER BY createdAt DESC;'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!results.length) return [];
|
|
||||||
|
|
||||||
const users: NDKCacheUserProfile[] = results.map((item) => ({
|
|
||||||
pubkey: item.pubkey,
|
|
||||||
...JSON.parse(item.profile as string),
|
|
||||||
}));
|
|
||||||
return users;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getCacheUser(pubkey: string) {
|
|
||||||
const results: Array<NDKCacheUser> = await this.db.select(
|
|
||||||
'SELECT * FROM ndk_users WHERE pubkey = $1 ORDER BY pubkey DESC LIMIT 1;',
|
|
||||||
[pubkey]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!results.length) return null;
|
|
||||||
|
|
||||||
if (typeof results[0].profile === 'string')
|
|
||||||
results[0].profile = JSON.parse(results[0].profile);
|
|
||||||
|
|
||||||
return results[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getCacheEvent(id: string) {
|
|
||||||
const results: Array<NDKCacheEvent> = await this.db.select(
|
|
||||||
'SELECT * FROM ndk_events WHERE id = $1 ORDER BY id DESC LIMIT 1;',
|
|
||||||
[id]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!results.length) return null;
|
|
||||||
return results[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getCacheEvents(ids: string[]) {
|
|
||||||
const idsArr = `'${ids.join("','")}'`;
|
|
||||||
|
|
||||||
const results: Array<NDKCacheEvent> = await this.db.select(
|
|
||||||
`SELECT * FROM ndk_events WHERE id IN (${idsArr}) ORDER BY id;`
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!results.length) return [];
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getCacheEventsByPubkey(pubkey: string) {
|
|
||||||
const results: Array<NDKCacheEvent> = await this.db.select(
|
|
||||||
'SELECT * FROM ndk_events WHERE pubkey = $1 ORDER BY id;',
|
|
||||||
[pubkey]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!results.length) return [];
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getCacheEventsByKind(kind: number) {
|
|
||||||
const results: Array<NDKCacheEvent> = await this.db.select(
|
|
||||||
'SELECT * FROM ndk_events WHERE kind = $1 ORDER BY id;',
|
|
||||||
[kind]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!results.length) return [];
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getCacheEventsByKindAndAuthor(kind: number, pubkey: string) {
|
|
||||||
const results: Array<NDKCacheEvent> = await this.db.select(
|
|
||||||
'SELECT * FROM ndk_events WHERE kind = $1 AND pubkey = $2 ORDER BY id;',
|
|
||||||
[kind, pubkey]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!results.length) return [];
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getCacheEventTagsByTagValue(tagValue: string) {
|
|
||||||
const results: Array<NDKCacheEventTag> = await this.db.select(
|
|
||||||
'SELECT * FROM ndk_eventtags WHERE tagValue = $1 ORDER BY id;',
|
|
||||||
[tagValue]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!results.length) return [];
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async setCacheEvent({
|
|
||||||
id,
|
|
||||||
pubkey,
|
|
||||||
content,
|
|
||||||
kind,
|
|
||||||
createdAt,
|
|
||||||
relay,
|
|
||||||
event,
|
|
||||||
}: NDKCacheEvent) {
|
|
||||||
return await this.db.execute(
|
|
||||||
'INSERT OR IGNORE INTO ndk_events (id, pubkey, content, kind, createdAt, relay, event) VALUES ($1, $2, $3, $4, $5, $6, $7);',
|
|
||||||
[id, pubkey, content, kind, createdAt, relay, event]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async setCacheEventTag({ id, eventId, tag, value, tagValue }: NDKCacheEventTag) {
|
|
||||||
return await this.db.execute(
|
|
||||||
'INSERT OR IGNORE INTO ndk_eventtags (id, eventId, tag, value, tagValue) VALUES ($1, $2, $3, $4, $5);',
|
|
||||||
[id, eventId, tag, value, tagValue]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async setCacheProfiles(profiles: Array<NDKCacheUser>) {
|
|
||||||
return await Promise.all(
|
|
||||||
profiles.map(
|
|
||||||
async (profile) =>
|
|
||||||
await this.db.execute(
|
|
||||||
'INSERT OR IGNORE INTO ndk_users (pubkey, profile, createdAt) VALUES ($1, $2, $3);',
|
|
||||||
[profile.pubkey, profile.profile, profile.createdAt]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async checkAccount() {
|
|
||||||
const result: Array<{ total: string }> = await this.db.select(
|
|
||||||
'SELECT COUNT(*) AS "total" FROM accounts WHERE is_active = "1" ORDER BY id DESC LIMIT 1;'
|
|
||||||
);
|
|
||||||
return parseInt(result[0].total);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getActiveAccount() {
|
|
||||||
const results: Array<Account> = await this.db.select(
|
|
||||||
'SELECT * FROM accounts WHERE is_active = "1" ORDER BY id DESC LIMIT 1;'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (results.length) {
|
|
||||||
this.account = results[0];
|
|
||||||
this.account.contacts = [];
|
|
||||||
} else {
|
|
||||||
console.log('no active account, please create new account');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async createAccount(npub: string, pubkey: string) {
|
|
||||||
const existAccounts: Array<Account> = await this.db.select(
|
|
||||||
'SELECT * FROM accounts WHERE pubkey = $1 ORDER BY id DESC LIMIT 1;',
|
|
||||||
[pubkey]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (existAccounts.length) {
|
|
||||||
await this.db.execute("UPDATE accounts SET is_active = '1' WHERE pubkey = $1;", [
|
|
||||||
pubkey,
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
await this.db.execute(
|
|
||||||
'INSERT OR IGNORE INTO accounts (id, pubkey, is_active) VALUES ($1, $2, $3);',
|
|
||||||
[npub, pubkey, 1]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return await this.getActiveAccount();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async updateAccount(column: string, value: string) {
|
|
||||||
const insert = await this.db.execute(
|
|
||||||
`UPDATE accounts SET ${column} = $1 WHERE id = $2;`,
|
|
||||||
[value, this.account.id]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (insert) {
|
|
||||||
const account = await this.getActiveAccount();
|
|
||||||
return account;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getWidgets() {
|
|
||||||
const widgets: Array<Widget> = await this.db.select(
|
|
||||||
'SELECT * FROM widgets WHERE account_id = $1 ORDER BY created_at DESC;',
|
|
||||||
[this.account.id]
|
|
||||||
);
|
|
||||||
return widgets;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async createWidget(kind: number, title: string, content: string | string[]) {
|
|
||||||
const insert = await this.db.execute(
|
|
||||||
'INSERT INTO widgets (account_id, kind, title, content) VALUES ($1, $2, $3, $4);',
|
|
||||||
[this.account.id, kind, title, content]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (insert) {
|
|
||||||
const widgets: Array<Widget> = await this.db.select(
|
|
||||||
'SELECT * FROM widgets ORDER BY id DESC LIMIT 1;'
|
|
||||||
);
|
|
||||||
if (widgets.length < 1) console.error('get created widget failed');
|
|
||||||
return widgets[0];
|
|
||||||
} else {
|
|
||||||
console.error('create widget failed');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async removeWidget(id: string) {
|
|
||||||
const res = await this.db.execute('DELETE FROM widgets WHERE id = $1;', [id]);
|
|
||||||
if (res) return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async createEvent(event: NDKEvent) {
|
|
||||||
const rawNostrEvent = rawEvent(event);
|
|
||||||
|
|
||||||
let root: string;
|
|
||||||
let reply: string;
|
|
||||||
|
|
||||||
if (event.tags?.[0]?.[0] === 'e' && !event.tags?.[0]?.[3]) {
|
|
||||||
root = event.tags[0][1];
|
|
||||||
} else {
|
|
||||||
root = event.tags.find((el) => el[3] === 'root')?.[1];
|
|
||||||
reply = event.tags.find((el) => el[3] === 'reply')?.[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
return await this.db.execute(
|
|
||||||
'INSERT OR IGNORE INTO events (id, account_id, event, author, kind, root_id, reply_id, created_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8);',
|
|
||||||
[
|
|
||||||
event.id,
|
|
||||||
this.account.id,
|
|
||||||
JSON.stringify(rawNostrEvent),
|
|
||||||
event.pubkey,
|
|
||||||
event.kind,
|
|
||||||
root,
|
|
||||||
reply,
|
|
||||||
event.created_at,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getEventByID(id: string) {
|
|
||||||
const results: DBEvent[] = await this.db.select(
|
|
||||||
'SELECT * FROM events WHERE id = $1 LIMIT 1;',
|
|
||||||
[id]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (results.length < 1) return null;
|
|
||||||
return JSON.parse(results[0].event as string) as NDKEvent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async countTotalEvents() {
|
|
||||||
const result: Array<{ total: string }> = await this.db.select(
|
|
||||||
'SELECT COUNT(*) AS "total" FROM events WHERE account_id = $1;',
|
|
||||||
[this.account.id]
|
|
||||||
);
|
|
||||||
return parseInt(result[0].total);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getAllEvents(limit: number, offset: number) {
|
|
||||||
const totalEvents = await this.countTotalEvents();
|
|
||||||
const nextCursor = offset + limit;
|
|
||||||
|
|
||||||
const events: { data: DBEvent[] | null; nextCursor: number } = {
|
|
||||||
data: null,
|
|
||||||
nextCursor: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
const query: DBEvent[] = await this.db.select(
|
|
||||||
'SELECT * FROM events WHERE account_id = $1 GROUP BY root_id ORDER BY created_at DESC LIMIT $2 OFFSET $3;',
|
|
||||||
[this.account.id, limit, offset]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (query && query.length > 0) {
|
|
||||||
events['data'] = query;
|
|
||||||
events['nextCursor'] =
|
|
||||||
Math.round(totalEvents / nextCursor) > 1 ? nextCursor : undefined;
|
|
||||||
|
|
||||||
return events;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
data: [],
|
|
||||||
nextCursor: 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getAllEventsByAuthors(authors: string[], limit: number, offset: number) {
|
|
||||||
const totalEvents = await this.countTotalEvents();
|
|
||||||
const nextCursor = offset + limit;
|
|
||||||
const authorsArr = `'${authors.join("','")}'`;
|
|
||||||
|
|
||||||
const events: { data: DBEvent[] | null; nextCursor: number } = {
|
|
||||||
data: null,
|
|
||||||
nextCursor: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
const query: DBEvent[] = await this.db.select(
|
|
||||||
`SELECT * FROM events WHERE author IN (${authorsArr}) ORDER BY created_at DESC LIMIT $1 OFFSET $2;`,
|
|
||||||
[limit, offset]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (query && query.length > 0) {
|
|
||||||
events['data'] = query;
|
|
||||||
events['nextCursor'] =
|
|
||||||
Math.round(totalEvents / nextCursor) > 1 ? nextCursor : undefined;
|
|
||||||
|
|
||||||
return events;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
data: [],
|
|
||||||
nextCursor: 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getAllEventsByKinds(kinds: number[], limit: number, offset: number) {
|
|
||||||
const totalEvents = await this.countTotalEvents();
|
|
||||||
const nextCursor = offset + limit;
|
|
||||||
const authorsArr = `'${kinds.join("','")}'`;
|
|
||||||
|
|
||||||
const events: { data: DBEvent[] | null; nextCursor: number } = {
|
|
||||||
data: null,
|
|
||||||
nextCursor: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
const query: DBEvent[] = await this.db.select(
|
|
||||||
`SELECT * FROM events WHERE kinds IN (${authorsArr}) AND account_id = $1 ORDER BY created_at DESC LIMIT $2 OFFSET $3;`,
|
|
||||||
[this.account.id, limit, offset]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (query && query.length > 0) {
|
|
||||||
events['data'] = query;
|
|
||||||
events['nextCursor'] =
|
|
||||||
Math.round(totalEvents / nextCursor) > 1 ? nextCursor : undefined;
|
|
||||||
|
|
||||||
return events;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
data: [],
|
|
||||||
nextCursor: 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public async isEventsEmpty() {
|
|
||||||
const results: DBEvent[] = await this.db.select(
|
|
||||||
'SELECT * FROM events WHERE account_id = $1 ORDER BY id DESC LIMIT 1;',
|
|
||||||
[this.account.id]
|
|
||||||
);
|
|
||||||
|
|
||||||
return results.length < 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async createRelay(relay: string, purpose?: string) {
|
|
||||||
const existRelays: Relays[] = await this.db.select(
|
|
||||||
'SELECT * FROM relays WHERE relay = $1 AND account_id = $2 ORDER BY id DESC LIMIT 1;',
|
|
||||||
[relay, this.account.id]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (existRelays.length) return;
|
|
||||||
|
|
||||||
return await this.db.execute(
|
|
||||||
'INSERT OR IGNORE INTO relays (account_id, relay, purpose) VALUES ($1, $2, $3);',
|
|
||||||
[this.account.id, relay, purpose || '']
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async removeRelay(relay: string) {
|
|
||||||
return await this.db.execute(`DELETE FROM relays WHERE relay = "${relay}";`);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async createSetting(key: string, value: string | undefined) {
|
|
||||||
if (value) {
|
|
||||||
return await this.db.execute(
|
|
||||||
'INSERT OR IGNORE INTO settings (key, value) VALUES ($1, $2);',
|
|
||||||
[key, value]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentSetting = await this.checkSettingValue(key);
|
|
||||||
|
|
||||||
if (!currentSetting)
|
|
||||||
return await this.db.execute(
|
|
||||||
'INSERT OR IGNORE INTO settings (key, value) VALUES ($1, $2);',
|
|
||||||
[key, value]
|
|
||||||
);
|
|
||||||
|
|
||||||
const currentValue = !!parseInt(currentSetting);
|
|
||||||
|
|
||||||
return await this.db.execute('UPDATE settings SET value = $1 WHERE key = $2;', [
|
|
||||||
+!currentValue,
|
|
||||||
key,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getAllSettings() {
|
|
||||||
const results: { key: string; value: string }[] = await this.db.select(
|
|
||||||
'SELECT * FROM settings ORDER BY id DESC;'
|
|
||||||
);
|
|
||||||
if (results.length < 1) return null;
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async checkSettingValue(key: string) {
|
|
||||||
const results: { key: string; value: string }[] = await this.db.select(
|
|
||||||
'SELECT * FROM settings WHERE key = $1 ORDER BY id DESC LIMIT 1;',
|
|
||||||
[key]
|
|
||||||
);
|
|
||||||
if (!results.length) return false;
|
|
||||||
return results[0].value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getSettingValue(key: string) {
|
|
||||||
const results: { key: string; value: string }[] = await this.db.select(
|
|
||||||
'SELECT * FROM settings WHERE key = $1 ORDER BY id DESC LIMIT 1;',
|
|
||||||
[key]
|
|
||||||
);
|
|
||||||
if (!results.length) return '0';
|
|
||||||
return results[0].value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async clearCache() {
|
|
||||||
await this.db.execute('DELETE FROM ndk_events;');
|
|
||||||
await this.db.execute('DELETE FROM ndk_eventtags;');
|
|
||||||
await this.db.execute('DELETE FROM ndk_users;');
|
|
||||||
}
|
|
||||||
|
|
||||||
public async accountLogout() {
|
|
||||||
// update current account status
|
|
||||||
await this.db.execute("UPDATE accounts SET is_active = '0' WHERE id = $1;", [
|
|
||||||
this.account.id,
|
|
||||||
]);
|
|
||||||
this.account = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async close() {
|
|
||||||
return this.db.close();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,128 +0,0 @@
|
|||||||
import { message } from '@tauri-apps/plugin-dialog';
|
|
||||||
import { platform } from '@tauri-apps/plugin-os';
|
|
||||||
import { relaunch } from '@tauri-apps/plugin-process';
|
|
||||||
import Database from '@tauri-apps/plugin-sql';
|
|
||||||
import { check } from '@tauri-apps/plugin-updater';
|
|
||||||
import Markdown from 'markdown-to-jsx';
|
|
||||||
import { PropsWithChildren, createContext, useContext, useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
import { LumeStorage } from '@libs/storage/instance';
|
|
||||||
|
|
||||||
import { LoaderIcon } from '@shared/icons';
|
|
||||||
|
|
||||||
import { QUOTES } from '@utils/constants';
|
|
||||||
|
|
||||||
interface StorageContext {
|
|
||||||
db: LumeStorage;
|
|
||||||
}
|
|
||||||
|
|
||||||
const StorageContext = createContext<StorageContext>({
|
|
||||||
db: undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
const StorageInstance = () => {
|
|
||||||
const [db, setDB] = useState<LumeStorage>(undefined);
|
|
||||||
const [isNewVersion, setIsNewVersion] = useState(false);
|
|
||||||
|
|
||||||
const initLumeStorage = async () => {
|
|
||||||
try {
|
|
||||||
const sqlite = await Database.load('sqlite:lume_v2.db');
|
|
||||||
const platformName = await platform();
|
|
||||||
|
|
||||||
const lumeStorage = new LumeStorage(sqlite, platformName);
|
|
||||||
if (!lumeStorage.account) await lumeStorage.getActiveAccount();
|
|
||||||
|
|
||||||
const settings = await lumeStorage.getAllSettings();
|
|
||||||
let autoUpdater = false;
|
|
||||||
|
|
||||||
if (settings) {
|
|
||||||
settings.forEach((item) => {
|
|
||||||
if (item.key === 'outbox') lumeStorage.settings.outbox = !!parseInt(item.value);
|
|
||||||
|
|
||||||
if (item.key === 'media') lumeStorage.settings.media = !!parseInt(item.value);
|
|
||||||
|
|
||||||
if (item.key === 'hashtag')
|
|
||||||
lumeStorage.settings.hashtag = !!parseInt(item.value);
|
|
||||||
|
|
||||||
if (item.key === 'autoupdate') {
|
|
||||||
if (parseInt(item.value)) autoUpdater = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (autoUpdater) {
|
|
||||||
// check update
|
|
||||||
const update = await check();
|
|
||||||
// install new version
|
|
||||||
if (update) {
|
|
||||||
setIsNewVersion(true);
|
|
||||||
|
|
||||||
await update.downloadAndInstall();
|
|
||||||
await relaunch();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setDB(lumeStorage);
|
|
||||||
} catch (e) {
|
|
||||||
await message(`Cannot initialize database: ${e}`, {
|
|
||||||
title: 'Lume',
|
|
||||||
type: 'error',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!db) initLumeStorage();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return { db, isNewVersion };
|
|
||||||
};
|
|
||||||
|
|
||||||
const StorageProvider = ({ children }: PropsWithChildren<object>) => {
|
|
||||||
const { db, isNewVersion } = StorageInstance();
|
|
||||||
|
|
||||||
if (!db)
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-tauri-drag-region
|
|
||||||
className="relative flex h-screen w-screen items-center justify-center bg-neutral-50 dark:bg-neutral-950"
|
|
||||||
>
|
|
||||||
<div className="flex max-w-2xl flex-col items-start gap-1">
|
|
||||||
<h5 className="font-semibold uppercase">TIP:</h5>
|
|
||||||
<Markdown
|
|
||||||
options={{
|
|
||||||
overrides: {
|
|
||||||
a: {
|
|
||||||
props: {
|
|
||||||
className: 'text-blue-500 hover:text-blue-600',
|
|
||||||
target: '_blank',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
className="text-4xl font-semibold leading-snug text-neutral-300 dark:text-neutral-700"
|
|
||||||
>
|
|
||||||
{QUOTES[Math.floor(Math.random() * QUOTES.length)]}
|
|
||||||
</Markdown>
|
|
||||||
</div>
|
|
||||||
<div className="absolute bottom-5 right-5 inline-flex items-center gap-2.5">
|
|
||||||
<LoaderIcon className="h-6 w-6 animate-spin text-blue-500" />
|
|
||||||
<p className="font-semibold">
|
|
||||||
{isNewVersion ? 'Found a new version, updating...' : 'Starting...'}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
return <StorageContext.Provider value={{ db }}>{children}</StorageContext.Provider>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const useStorage = () => {
|
|
||||||
const context = useContext(StorageContext);
|
|
||||||
if (context === undefined) {
|
|
||||||
throw new Error('Storage not found');
|
|
||||||
}
|
|
||||||
return context;
|
|
||||||
};
|
|
||||||
|
|
||||||
export { StorageProvider, useStorage };
|
|
@ -15,24 +15,21 @@ import { User } from '@shared/user';
|
|||||||
|
|
||||||
export function Repost({ event }: { event: NDKEvent }) {
|
export function Repost({ event }: { event: NDKEvent }) {
|
||||||
const { ark } = useArk();
|
const { ark } = useArk();
|
||||||
const { status, data: repostEvent } = useQuery({
|
const {
|
||||||
|
isLoading,
|
||||||
|
isError,
|
||||||
|
data: repostEvent,
|
||||||
|
} = useQuery({
|
||||||
queryKey: ['repost', event.id],
|
queryKey: ['repost', event.id],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
try {
|
try {
|
||||||
let event: NDKEvent = undefined;
|
|
||||||
|
|
||||||
if (event.content.length > 50) {
|
if (event.content.length > 50) {
|
||||||
const embed = JSON.parse(event.content) as NostrEvent;
|
const embed = JSON.parse(event.content) as NostrEvent;
|
||||||
event = ark.createNDKEvent({ event: embed });
|
return ark.createNDKEvent({ event: embed });
|
||||||
}
|
}
|
||||||
|
|
||||||
const id = event.tags.find((el) => el[0] === 'e')[1];
|
const id = event.tags.find((el) => el[0] === 'e')[1];
|
||||||
if (!id) throw new Error('Failed to get repost event id');
|
return await ark.getEventById({ id });
|
||||||
|
|
||||||
event = await ark.getEventById({ id });
|
|
||||||
|
|
||||||
if (!event) return Promise.reject(new Error('Failed to get repost event'));
|
|
||||||
return event;
|
|
||||||
} catch {
|
} catch {
|
||||||
throw new Error('Failed to get repost event');
|
throw new Error('Failed to get repost event');
|
||||||
}
|
}
|
||||||
@ -54,7 +51,7 @@ export function Repost({ event }: { event: NDKEvent }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (status === 'pending') {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="w-full px-3 pb-3">
|
<div className="w-full px-3 pb-3">
|
||||||
<NoteSkeleton />
|
<NoteSkeleton />
|
||||||
@ -62,6 +59,21 @@ export function Repost({ event }: { event: NDKEvent }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isError) {
|
||||||
|
return (
|
||||||
|
<div className="mb-3 h-min w-full px-3">
|
||||||
|
<div className="relative flex flex-col gap-2 overflow-hidden rounded-xl bg-neutral-50 pt-3 dark:bg-neutral-950">
|
||||||
|
<User pubkey={event.pubkey} time={event.created_at} variant="repost" />
|
||||||
|
<div className="relative flex flex-col gap-2">
|
||||||
|
<div className="px-3">
|
||||||
|
<p>Failed to load event</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-3 h-min w-full px-3">
|
<div className="mb-3 h-min w-full px-3">
|
||||||
<div className="relative flex flex-col gap-2 overflow-hidden rounded-xl bg-neutral-50 pt-3 dark:bg-neutral-950">
|
<div className="relative flex flex-col gap-2 overflow-hidden rounded-xl bg-neutral-50 pt-3 dark:bg-neutral-950">
|
||||||
|
@ -33,7 +33,9 @@ export function NewsfeedWidget() {
|
|||||||
const events = await ark.getInfiniteEvents({
|
const events = await ark.getInfiniteEvents({
|
||||||
filter: {
|
filter: {
|
||||||
kinds: [NDKKind.Text, NDKKind.Repost],
|
kinds: [NDKKind.Text, NDKKind.Repost],
|
||||||
authors: ark.account.contacts,
|
authors: !ark.account.contacts.length
|
||||||
|
? [ark.account.pubkey]
|
||||||
|
: ark.account.contacts,
|
||||||
},
|
},
|
||||||
limit: FETCH_LIMIT,
|
limit: FETCH_LIMIT,
|
||||||
pageParam,
|
pageParam,
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
|
import { NDKEvent, NDKKind, NDKSubscription } from '@nostr-dev-kit/ndk';
|
||||||
import { useInfiniteQuery, useQueryClient } from '@tanstack/react-query';
|
import { useInfiniteQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
import { useCallback, useEffect, useMemo } from 'react';
|
import { useCallback, useEffect, useMemo } from 'react';
|
||||||
import { VList } from 'virtua';
|
import { VList } from 'virtua';
|
||||||
|
|
||||||
import { useArk } from '@libs/ark';
|
import { useArk } from '@libs/ark';
|
||||||
import { useNDK } from '@libs/ndk/provider';
|
|
||||||
import { useStorage } from '@libs/storage/provider';
|
|
||||||
|
|
||||||
import { ArrowRightCircleIcon, LoaderIcon } from '@shared/icons';
|
import { ArrowRightCircleIcon, LoaderIcon } from '@shared/icons';
|
||||||
import { MemoizedNotifyNote, NoteSkeleton } from '@shared/notes';
|
import { MemoizedNotifyNote, NoteSkeleton } from '@shared/notes';
|
||||||
@ -30,21 +28,17 @@ export function NotificationWidget() {
|
|||||||
signal: AbortSignal;
|
signal: AbortSignal;
|
||||||
pageParam: number;
|
pageParam: number;
|
||||||
}) => {
|
}) => {
|
||||||
const events = await fetcher.fetchLatestEvents(
|
const events = await ark.getInfiniteEvents({
|
||||||
relayUrls,
|
filter: {
|
||||||
{
|
|
||||||
kinds: [NDKKind.Text, NDKKind.Repost, NDKKind.Reaction, NDKKind.Zap],
|
kinds: [NDKKind.Text, NDKKind.Repost, NDKKind.Reaction, NDKKind.Zap],
|
||||||
'#p': [db.account.pubkey],
|
'#p': [ark.account.pubkey],
|
||||||
},
|
},
|
||||||
FETCH_LIMIT,
|
limit: FETCH_LIMIT,
|
||||||
{ asOf: pageParam === 0 ? undefined : pageParam, abortSignal: signal }
|
pageParam,
|
||||||
);
|
signal,
|
||||||
|
|
||||||
const ndkEvents = events.map((event) => {
|
|
||||||
return new NDKEvent(ndk, event);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return ndkEvents.sort((a, b) => b.created_at - a.created_at);
|
return events;
|
||||||
},
|
},
|
||||||
getNextPageParam: (lastPage) => {
|
getNextPageParam: (lastPage) => {
|
||||||
const lastEvent = lastPage.at(-1);
|
const lastEvent = lastPage.at(-1);
|
||||||
@ -63,21 +57,24 @@ export function NotificationWidget() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const renderEvent = useCallback((event: NDKEvent) => {
|
const renderEvent = useCallback((event: NDKEvent) => {
|
||||||
if (event.pubkey === db.account.pubkey) return null;
|
if (event.pubkey === ark.account.pubkey) return null;
|
||||||
return <MemoizedNotifyNote key={event.id} event={event} />;
|
return <MemoizedNotifyNote key={event.id} event={event} />;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (status === 'success' && db.account) {
|
let sub: NDKSubscription = undefined;
|
||||||
|
|
||||||
|
if (status === 'success' && ark.account) {
|
||||||
const filter = {
|
const filter = {
|
||||||
kinds: [NDKKind.Text, NDKKind.Repost, NDKKind.Reaction, NDKKind.Zap],
|
kinds: [NDKKind.Text, NDKKind.Repost, NDKKind.Reaction, NDKKind.Zap],
|
||||||
'#p': [db.account.pubkey],
|
'#p': [ark.account.pubkey],
|
||||||
since: Math.floor(Date.now() / 1000),
|
since: Math.floor(Date.now() / 1000),
|
||||||
};
|
};
|
||||||
|
|
||||||
sub(
|
sub = ark.subscribe({
|
||||||
filter,
|
filter,
|
||||||
async (event) => {
|
closeOnEose: false,
|
||||||
|
cb: async (event) => {
|
||||||
queryClient.setQueryData(
|
queryClient.setQueryData(
|
||||||
['notification'],
|
['notification'],
|
||||||
(prev: { pageParams: number; pages: Array<NDKEvent[]> }) => ({
|
(prev: { pageParams: number; pages: Array<NDKEvent[]> }) => ({
|
||||||
@ -86,21 +83,18 @@ export function NotificationWidget() {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const user = ndk.getUser({ pubkey: event.pubkey });
|
const profile = await ark.getUserProfile({ pubkey: event.pubkey });
|
||||||
await user.fetchProfile();
|
|
||||||
|
|
||||||
switch (event.kind) {
|
switch (event.kind) {
|
||||||
case NDKKind.Text:
|
case NDKKind.Text:
|
||||||
return await sendNativeNotification(
|
return await sendNativeNotification(
|
||||||
`${
|
`${profile.displayName || profile.name} has replied to your note`
|
||||||
user.profile.displayName || user.profile.name
|
|
||||||
} has replied to your note`
|
|
||||||
);
|
);
|
||||||
case NDKKind.EncryptedDirectMessage: {
|
case NDKKind.EncryptedDirectMessage: {
|
||||||
if (location.pathname !== '/chats') {
|
if (location.pathname !== '/chats') {
|
||||||
return await sendNativeNotification(
|
return await sendNativeNotification(
|
||||||
`${
|
`${
|
||||||
user.profile.displayName || user.profile.name
|
profile.displayName || profile.name
|
||||||
} has send you a encrypted message`
|
} has send you a encrypted message`
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@ -109,28 +103,28 @@ export function NotificationWidget() {
|
|||||||
}
|
}
|
||||||
case NDKKind.Repost:
|
case NDKKind.Repost:
|
||||||
return await sendNativeNotification(
|
return await sendNativeNotification(
|
||||||
`${
|
`${profile.displayName || profile.name} has reposted to your note`
|
||||||
user.profile.displayName || user.profile.name
|
|
||||||
} has reposted to your note`
|
|
||||||
);
|
);
|
||||||
case NDKKind.Reaction:
|
case NDKKind.Reaction:
|
||||||
return await sendNativeNotification(
|
return await sendNativeNotification(
|
||||||
`${user.profile.displayName || user.profile.name} has reacted ${
|
`${profile.displayName || profile.name} has reacted ${
|
||||||
event.content
|
event.content
|
||||||
} to your note`
|
} to your note`
|
||||||
);
|
);
|
||||||
case NDKKind.Zap:
|
case NDKKind.Zap:
|
||||||
return await sendNativeNotification(
|
return await sendNativeNotification(
|
||||||
`${user.profile.displayName || user.profile.name} has zapped to your note`
|
`${profile.displayName || profile.name} has zapped to your note`
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
false,
|
});
|
||||||
'notification'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (sub) sub.stop();
|
||||||
|
};
|
||||||
}, [status]);
|
}, [status]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
Loading…
Reference in New Issue
Block a user