This commit is contained in:
Ren Amamiya 2023-05-24 11:34:26 +07:00
parent 6d43d2c53a
commit 221ff52a3f
13 changed files with 60 additions and 144 deletions

View File

@ -15,7 +15,6 @@
"dependencies": {
"@floating-ui/react": "^0.23.1",
"@headlessui/react": "^1.7.14",
"@nostr-connect/connect": "^0.4.0",
"@tanstack/react-query": "^4.29.7",
"@tanstack/react-virtual": "3.0.0-beta.54",
"@tauri-apps/api": "^1.3.0",
@ -30,7 +29,7 @@
"react-dom": "^18.2.0",
"react-hook-form": "^7.43.9",
"react-markdown": "^8.0.7",
"react-virtuoso": "^4.3.7",
"react-virtuoso": "^4.3.8",
"remark-gfm": "^3.0.1",
"slate": "^0.94.1",
"slate-history": "^0.93.0",
@ -43,7 +42,7 @@
"devDependencies": {
"@tailwindcss/typography": "^0.5.9",
"@tauri-apps/cli": "^1.3.1",
"@types/node": "^18.16.13",
"@types/node": "^18.16.14",
"@types/react": "^18.2.6",
"@types/react-dom": "^18.2.4",
"@types/youtube-player": "^5.5.7",

View File

@ -7,9 +7,6 @@ dependencies:
'@headlessui/react':
specifier: ^1.7.14
version: 1.7.14(react-dom@18.2.0)(react@18.2.0)
'@nostr-connect/connect':
specifier: ^0.4.0
version: 0.4.0(react@18.2.0)
'@tanstack/react-query':
specifier: ^4.29.7
version: 4.29.7(react-dom@18.2.0)(react@18.2.0)
@ -53,8 +50,8 @@ dependencies:
specifier: ^8.0.7
version: 8.0.7(@types/react@18.2.6)(react@18.2.0)
react-virtuoso:
specifier: ^4.3.7
version: 4.3.7(react-dom@18.2.0)(react@18.2.0)
specifier: ^4.3.8
version: 4.3.8(react-dom@18.2.0)(react@18.2.0)
remark-gfm:
specifier: ^3.0.1
version: 3.0.1
@ -88,8 +85,8 @@ devDependencies:
specifier: ^1.3.1
version: 1.3.1
'@types/node':
specifier: ^18.16.13
version: 18.16.13
specifier: ^18.16.14
version: 18.16.14
'@types/react':
specifier: ^18.2.6
version: 18.2.6
@ -137,7 +134,7 @@ devDependencies:
version: 4.9.5
vite:
specifier: ^4.3.8
version: 4.3.8(@types/node@18.16.13)
version: 4.3.8(@types/node@18.16.14)
vite-plugin-ssr:
specifier: ^0.4.126
version: 0.4.126(vite@4.3.8)
@ -499,17 +496,6 @@ packages:
fastq: 1.15.0
dev: true
/@nostr-connect/connect@0.4.0(react@18.2.0):
resolution: {integrity: sha512-N8ubuLQb85veXK35XGlt+vI9JljhODKj3NRMfUTFuA3kNDpzW8ZRtGzIAPifrL9uZ8VNjAaU5fa7lMOHpMca0w==}
engines: {node: '>=10'}
peerDependencies:
react: '>=16'
dependencies:
events: 3.3.0
nostr-tools: 1.11.1
react: 18.2.0
dev: false
/@polka/url@1.0.0-next.21:
resolution: {integrity: sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==}
dev: true
@ -852,8 +838,8 @@ packages:
'@tauri-apps/cli-win32-x64-msvc': 1.3.1
dev: true
/@types/debug@4.1.7:
resolution: {integrity: sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==}
/@types/debug@4.1.8:
resolution: {integrity: sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==}
dependencies:
'@types/ms': 0.7.31
dev: false
@ -882,8 +868,8 @@ packages:
resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==}
dev: false
/@types/node@18.16.13:
resolution: {integrity: sha512-uZRomboV1vBL61EBXneL4j9/hEn+1Yqa4LQdpGrKmXFyJmVfWc9JV9+yb2AlnOnuaDnb2PDO3hC6/LKmzJxP1A==}
/@types/node@18.16.14:
resolution: {integrity: sha512-+ImzUB3mw2c5ISJUq0punjDilUQ5GnUim0ZRvchHIWJmOC0G+p0kzhXBqj6cDjK0QdPFwzrHWgrJp3RPvCG5qg==}
dev: true
/@types/prop-types@15.7.5:
@ -936,7 +922,7 @@ packages:
vite: ^4
dependencies:
'@swc/core': 1.3.59
vite: 4.3.8(@types/node@18.16.13)
vite: 4.3.8(@types/node@18.16.14)
transitivePeerDependencies:
- '@swc/helpers'
dev: true
@ -1020,7 +1006,7 @@ packages:
postcss: ^8.1.0
dependencies:
browserslist: 4.21.5
caniuse-lite: 1.0.30001488
caniuse-lite: 1.0.30001489
fraction.js: 4.2.0
normalize-range: 0.1.2
picocolors: 1.0.0
@ -1060,9 +1046,9 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
dependencies:
caniuse-lite: 1.0.30001488
electron-to-chromium: 1.4.402
node-releases: 2.0.11
caniuse-lite: 1.0.30001489
electron-to-chromium: 1.4.405
node-releases: 2.0.12
update-browserslist-db: 1.0.11(browserslist@4.21.5)
dev: true
@ -1081,8 +1067,8 @@ packages:
engines: {node: '>= 6'}
dev: true
/caniuse-lite@1.0.30001488:
resolution: {integrity: sha512-NORIQuuL4xGpIy6iCCQGN4iFjlBXtfKWIenlUuyZJumLRIindLb7wXM+GO8erEhb7vXfcnf4BAg2PrSDN5TNLQ==}
/caniuse-lite@1.0.30001489:
resolution: {integrity: sha512-x1mgZEXK8jHIfAxm+xgdpHpk50IN3z3q3zP261/WS+uvePxW8izXuCu6AHz0lkuYTlATDehiZ/tNyYBdSQsOUQ==}
dev: true
/ccount@2.0.1:
@ -1260,8 +1246,8 @@ packages:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
dev: true
/electron-to-chromium@1.4.402:
resolution: {integrity: sha512-gWYvJSkohOiBE6ecVYXkrDgNaUjo47QEKK0kQzmWyhkH+yoYiG44bwuicTGNSIQRG3WDMsWVZJLRnJnLNkbWvA==}
/electron-to-chromium@1.4.405:
resolution: {integrity: sha512-JdDgnwU69FMZURoesf9gNOej2Cms1XJFfLk24y1IBtnAdhTcJY/mXnokmpmxHN59PcykBP4bgUU98vLY44Lhuw==}
dev: true
/emoji-regex@8.0.0:
@ -1322,11 +1308,6 @@ packages:
engines: {node: '>=12'}
dev: false
/events@3.3.0:
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
engines: {node: '>=0.8.x'}
dev: false
/execa@7.1.1:
resolution: {integrity: sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q==}
engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0}
@ -1614,7 +1595,7 @@ packages:
object-inspect: 1.12.3
pidtree: 0.6.0
string-argv: 0.3.2
yaml: 2.2.2
yaml: 2.3.0
transitivePeerDependencies:
- enquirer
- supports-color
@ -2064,7 +2045,7 @@ packages:
/micromark@3.1.0:
resolution: {integrity: sha512-6Mj0yHLdUZjHnOPgr5xfWIMqMWS12zDN6iws9SLuSz76W8jTtAv24MN4/CL7gJrl5vtxGInkkqDv/JIoRsQOvA==}
dependencies:
'@types/debug': 4.1.7
'@types/debug': 4.1.8
debug: 4.3.4
decode-named-character-reference: 1.0.2
micromark-core-commonmark: 1.0.6
@ -2136,8 +2117,8 @@ packages:
hasBin: true
dev: true
/node-releases@2.0.11:
resolution: {integrity: sha512-+M0PwXeU80kRohZ3aT4J/OnR+l9/KD2nVLNNoRgFtnf+umQVFdGBAO2N8+nCnEi0xlh/Wk3zOGC+vNNx+uM79Q==}
/node-releases@2.0.12:
resolution: {integrity: sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==}
dev: true
/normalize-path@3.0.0:
@ -2298,7 +2279,7 @@ packages:
dependencies:
lilconfig: 2.1.0
postcss: 8.4.23
yaml: 2.2.2
yaml: 2.3.0
dev: true
/postcss-nested@6.0.1(postcss@8.4.23):
@ -2408,8 +2389,8 @@ packages:
- supports-color
dev: false
/react-virtuoso@4.3.7(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-XPNRzmhXUyBoXjPxNYdqD5wubNXtDIbBFbhTR4awx4yEC98EegM5RLeaghIK0BBAhZyRFu8sMvrPnwE12KLOJg==}
/react-virtuoso@4.3.8(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-hoZj8Dl1R9fYqtUwA5LjCii1djO4ZNtoYkYsR52ZjdJzdXh2hec1IQ2O+1MZNjLTb4v8ff3hbt34StiHTVDdlg==}
engines: {node: '>=10'}
peerDependencies:
react: '>=16 || >=17 || >= 18'
@ -2495,8 +2476,8 @@ packages:
resolution: {integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==}
dev: true
/rollup@3.22.0:
resolution: {integrity: sha512-imsigcWor5Y/dC0rz2q0bBt9PabcL3TORry2hAa6O6BuMvY71bqHyfReAz5qyAqiQATD1m70qdntqBfBQjVWpQ==}
/rollup@3.23.0:
resolution: {integrity: sha512-h31UlwEi7FHihLe1zbk+3Q7z1k/84rb9BSwmBSr/XjOCEaBJ2YyedQDuM0t/kfOS0IxM+vk1/zI9XxYj9V+NJQ==}
engines: {node: '>=14.18.0', npm: '>=8.0.0'}
hasBin: true
optionalDependencies:
@ -2987,7 +2968,7 @@ packages:
esbuild: 0.17.19
fast-glob: 3.2.12
sirv: 2.0.3
vite: 4.3.8(@types/node@18.16.13)
vite: 4.3.8(@types/node@18.16.14)
dev: true
/vite-plugin-top-level-await@1.3.1(vite@4.3.8):
@ -2998,7 +2979,7 @@ packages:
'@rollup/plugin-virtual': 3.0.1
'@swc/core': 1.3.59
uuid: 9.0.0
vite: 4.3.8(@types/node@18.16.13)
vite: 4.3.8(@types/node@18.16.14)
transitivePeerDependencies:
- '@swc/helpers'
- rollup
@ -3015,13 +2996,13 @@ packages:
debug: 4.3.4
globrex: 0.1.2
tsconfck: 2.1.1(typescript@4.9.5)
vite: 4.3.8(@types/node@18.16.13)
vite: 4.3.8(@types/node@18.16.14)
transitivePeerDependencies:
- supports-color
- typescript
dev: true
/vite@4.3.8(@types/node@18.16.13):
/vite@4.3.8(@types/node@18.16.14):
resolution: {integrity: sha512-uYB8PwN7hbMrf4j1xzGDk/lqjsZvCDbt/JC5dyfxc19Pg8kRm14LinK/uq+HSLNswZEoKmweGdtpbnxRtrAXiQ==}
engines: {node: ^14.18.0 || >=16.0.0}
hasBin: true
@ -3046,10 +3027,10 @@ packages:
terser:
optional: true
dependencies:
'@types/node': 18.16.13
'@types/node': 18.16.14
esbuild: 0.17.19
postcss: 8.4.23
rollup: 3.22.0
rollup: 3.23.0
optionalDependencies:
fsevents: 2.3.2
dev: true
@ -3096,9 +3077,9 @@ packages:
utf-8-validate:
optional: true
/yaml@2.2.2:
resolution: {integrity: sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==}
engines: {node: '>= 14'}
/yaml@2.3.0:
resolution: {integrity: sha512-8/1wgzdKc7bc9E6my5wZjmdavHLvO/QOmLG1FBugblEvY4IXrLjlViIOmL24HthU042lWTDRO90Fz1Yp66UnMw==}
engines: {node: '>= 14', npm: '>= 7'}
dev: true
/zwitch@2.0.4:

View File

@ -60,29 +60,6 @@ export function Page() {
<h1 className="text-xl font-semibold text-white">Import your key</h1>
</div>
<div className="flex flex-col gap-4">
<div>
<button
type="button"
className="inline-flex w-full transform items-center justify-center gap-1.5 rounded-lg bg-zinc-900 ring-1 ring-zinc-800 px-3.5 py-2.5 font-medium text-zinc-400 active:translate-y-1"
>
<div className="inline-flex items-center rounded-md bg-zinc-400/10 px-2 py-0.5 text-base font-medium ring-1 ring-inset ring-zinc-400/20">
<span className="bg-gradient-to-r from-fuchsia-300 via-orange-100 to-amber-300 bg-clip-text text-transparent">
Coming soon
</span>
</div>
<span>Continue with Nostr Connect</span>
</button>
</div>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-zinc-800" />
</div>
<div className="relative flex justify-center">
<span className="bg-zinc-950 px-2 text-base text-zinc-500">
or
</span>
</div>
</div>
<form
onSubmit={handleSubmit(onSubmit)}
className="flex flex-col gap-3"

View File

@ -1,10 +1,7 @@
import { Image } from "@shared/image";
import { DEFAULT_AVATAR, IMGPROXY_URL } from "@stores/constants";
import { DEFAULT_AVATAR } from "@stores/constants";
import { useProfile } from "@utils/hooks/useProfile";
import { shortenKey } from "@utils/shortenKey";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
@ -31,9 +28,7 @@ export default function ChannelMessageUser({
<>
<div className="relative h-9 w-9 shrink rounded-md">
<Image
src={`${IMGPROXY_URL}/rs:fit:100:100/plain/${
user?.picture ? user.picture : DEFAULT_AVATAR
}`}
src={user?.picture || DEFAULT_AVATAR}
alt={pubkey}
className="h-9 w-9 rounded-md object-cover"
/>

View File

@ -1,7 +1,5 @@
import { Image } from "@shared/image";
import { DEFAULT_AVATAR, IMGPROXY_URL } from "@stores/constants";
import { DEFAULT_AVATAR } from "@stores/constants";
import { useProfile } from "@utils/hooks/useProfile";
import { shortenKey } from "@utils/shortenKey";
@ -19,9 +17,7 @@ export default function UserReply({ pubkey }: { pubkey: string }) {
<>
<div className="relative h-7 w-7 shrink overflow-hidden rounded">
<Image
src={`${IMGPROXY_URL}/rs:fit:100:100/plain/${
user?.picture ? user.picture : DEFAULT_AVATAR
}`}
src={user?.picture || DEFAULT_AVATAR}
alt={pubkey}
className="h-7 w-7 rounded object-cover"
/>

View File

@ -1,10 +1,7 @@
import { Image } from "@shared/image";
import { DEFAULT_AVATAR, IMGPROXY_URL } from "@stores/constants";
import { DEFAULT_AVATAR } from "@stores/constants";
import { useProfile } from "@utils/hooks/useProfile";
import { shortenKey } from "@utils/shortenKey";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
@ -31,9 +28,7 @@ export default function ChatMessageUser({
<>
<div className="relative h-9 w-9 shrink rounded-md">
<Image
src={`${IMGPROXY_URL}/rs:fit:100:100/plain/${
user?.picture ? user.picture : DEFAULT_AVATAR
}`}
src={user?.picture || DEFAULT_AVATAR}
alt={pubkey}
className="h-9 w-9 rounded-md object-cover"
/>

View File

@ -1,11 +1,8 @@
import { Popover, Transition } from "@headlessui/react";
import { Image } from "@shared/image";
import { DEFAULT_AVATAR, IMGPROXY_URL } from "@stores/constants";
import { DEFAULT_AVATAR } from "@stores/constants";
import { useProfile } from "@utils/hooks/useProfile";
import { shortenKey } from "@utils/shortenKey";
import { Popover, Transition } from "@headlessui/react";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
import { Fragment } from "react";
@ -22,15 +19,13 @@ export function NoteDefaultUser({
<Popover className="relative flex items-start gap-3">
<Popover.Button className="h-11 w-11 shrink-0 overflow-hidden rounded-md bg-zinc-900">
<Image
src={`${IMGPROXY_URL}/rs:fit:100:100/plain/${
user?.picture ? user.picture : DEFAULT_AVATAR
}`}
src={user?.picture || DEFAULT_AVATAR}
alt={pubkey}
className="h-11 w-11 object-cover"
/>
</Popover.Button>
<div className="flex flex-wrap items-baseline gap-1">
<h5 className="max-w-[15rem] text-base font-semibold leading-none truncate">
<h5 className="max-w-[10rem] text-base font-semibold leading-none truncate">
{user?.nip05 || user?.name || shortenKey(pubkey)}
</h5>
<span className="leading-none text-zinc-500">·</span>
@ -55,9 +50,7 @@ export function NoteDefaultUser({
>
<div className="flex items-start gap-2.5 border-b border-zinc-800 px-3 py-3">
<Image
src={`${IMGPROXY_URL}/rs:fit:200:200/plain/${
user?.picture ? user.picture : DEFAULT_AVATAR
}`}
src={user?.picture || DEFAULT_AVATAR}
alt={pubkey}
className="h-14 w-14 shrink-0 rounded-lg object-cover"
/>

View File

@ -1,10 +1,7 @@
import { Image } from "@shared/image";
import { DEFAULT_AVATAR, IMGPROXY_URL } from "@stores/constants";
import { DEFAULT_AVATAR } from "@stores/constants";
import { useProfile } from "@utils/hooks/useProfile";
import { shortenKey } from "@utils/shortenKey";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
@ -20,9 +17,7 @@ export default function NoteReplyUser({
<div className="group flex items-start gap-2.5">
<div className="relative h-11 w-11 shrink-0 rounded-md">
<Image
src={`${IMGPROXY_URL}/rs:fit:100:100/plain/${
user?.picture ? user.picture : DEFAULT_AVATAR
}`}
src={user?.picture || DEFAULT_AVATAR}
alt={pubkey}
className="h-11 w-11 rounded-md object-cover"
/>

View File

@ -1,11 +1,8 @@
import { Popover, Transition } from "@headlessui/react";
import { Image } from "@shared/image";
import { DEFAULT_AVATAR, IMGPROXY_URL } from "@stores/constants";
import { DEFAULT_AVATAR } from "@stores/constants";
import { useProfile } from "@utils/hooks/useProfile";
import { shortenKey } from "@utils/shortenKey";
import { Popover, Transition } from "@headlessui/react";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
import { Fragment } from "react";
@ -22,9 +19,7 @@ export function NoteRepostUser({
<Popover className="relative flex items-start gap-3">
<Popover.Button className="h-11 w-11 shrink-0 overflow-hidden rounded-md bg-zinc-900">
<Image
src={`${IMGPROXY_URL}/rs:fit:100:100/plain/${
user?.picture ? user.picture : DEFAULT_AVATAR
}`}
src={user?.picture || DEFAULT_AVATAR}
alt={pubkey}
className="h-11 w-11 rounded-md object-cover"
/>
@ -59,9 +54,7 @@ export function NoteRepostUser({
>
<div className="flex items-start gap-2.5 border-b border-zinc-800 px-3 py-3">
<Image
src={`${IMGPROXY_URL}/rs:fit:200:200/plain/${
user?.picture ? user.picture : DEFAULT_AVATAR
}`}
src={user?.picture || DEFAULT_AVATAR}
alt={pubkey}
className="h-14 w-14 shrink-0 rounded-lg object-cover"
/>

View File

@ -1,5 +1,5 @@
import { Image } from "@shared/image";
import { DEFAULT_AVATAR, IMGPROXY_URL } from "@stores/constants";
import { DEFAULT_AVATAR } from "@stores/constants";
import { useProfile } from "@utils/hooks/useProfile";
import { shortenKey } from "@utils/shortenKey";
import dayjs from "dayjs";
@ -14,9 +14,7 @@ export function ThreadAuthor({
<div className="relative flex items-center gap-2.5">
<div className="h-9 w-9 shrink-0 overflow-hidden rounded-md bg-zinc-900">
<Image
src={`${IMGPROXY_URL}/rs:fit:100:100/plain/${
user?.picture ? user.picture : DEFAULT_AVATAR
}`}
src={user?.picture || DEFAULT_AVATAR}
alt={pubkey}
className="h-9 w-9 object-cover"
/>

View File

@ -1,6 +1,5 @@
import { Image } from "@shared/image";
import { DEFAULT_AVATAR, IMGPROXY_URL } from "@stores/constants";
import { DEFAULT_AVATAR } from "@stores/constants";
export function User({ data }: { data: any }) {
const metadata = JSON.parse(data.metadata);
@ -9,9 +8,7 @@ export function User({ data }: { data: any }) {
<div className="flex items-center gap-2">
<div className="h-8 w-8 shrink-0 overflow-hidden rounded bg-zinc-900">
<Image
src={`${IMGPROXY_URL}/rs:fit:100:100/plain/${
metadata?.picture ? metadata.picture : DEFAULT_AVATAR
}`}
src={metadata?.picture || DEFAULT_AVATAR}
alt={data.pubkey}
className="h-8 w-8 object-cover"
loading="auto"

View File

@ -5,11 +5,8 @@ export const DEFAULT_AVATAR = "https://void.cat/d/KmypFh2fBdYCEvyJrPiN89.webp";
export const DEFAULT_CHANNEL_BANNER =
"https://bafybeiacwit7hjmdefqggxqtgh6ht5dhth7ndptwn2msl5kpkodudsr7py.ipfs.w3s.link/banner-1.jpg";
// img proxy
export const IMGPROXY_URL = "https://imgproxy.iris.to/insecure";
// metadata service
export const METADATA_SERVICE = "https://us.rbr.bio";
export const METADATA_SERVICE = "https://rbr.bio";
// read-only relay list
export const READONLY_RELAYS = [