2023-11-21 07:20:39 +00:00
|
|
|
/** @jsx h */
|
|
|
|
import { Component, ComponentChildren, Fragment, h } from "https://esm.sh/preact@10.17.1";
|
|
|
|
import { CopyButton } from "./components/copy-button.tsx";
|
|
|
|
import { CenterClass, InputClass } from "./components/tw.ts";
|
|
|
|
import {
|
2024-03-17 07:07:42 +00:00
|
|
|
BackgroundColor_HoverButton,
|
2023-11-21 07:20:39 +00:00
|
|
|
ErrorColor,
|
|
|
|
HintTextColor,
|
|
|
|
PrimaryTextColor,
|
|
|
|
SecondaryBackgroundColor,
|
|
|
|
TitleIconColor,
|
|
|
|
} from "./style/colors.ts";
|
|
|
|
import { Avatar } from "./components/avatar.tsx";
|
|
|
|
import { ProfileGetter } from "./search.tsx";
|
2024-01-01 17:28:10 +00:00
|
|
|
import { PublicKey } from "../../libs/nostr.ts/key.ts";
|
2023-11-21 07:20:39 +00:00
|
|
|
import { Loading } from "./components/loading.tsx";
|
|
|
|
import { RelayIcon } from "./icons/relay-icon.tsx";
|
2024-03-17 07:07:42 +00:00
|
|
|
import { Profile_Nostr_Event } from "../nostr.ts";
|
|
|
|
import { emitFunc } from "../event-bus.ts";
|
|
|
|
import { SelectConversation } from "./search_model.ts";
|
2023-11-21 07:20:39 +00:00
|
|
|
|
2024-03-15 13:44:17 +00:00
|
|
|
export type RelayInformation = {
|
2023-11-21 07:20:39 +00:00
|
|
|
name?: string;
|
|
|
|
description?: string;
|
|
|
|
pubkey?: string;
|
|
|
|
contact?: string;
|
|
|
|
supported_nips?: number[];
|
|
|
|
software?: string;
|
|
|
|
version?: string;
|
2024-03-15 13:44:17 +00:00
|
|
|
icon?: string;
|
2023-11-21 07:20:39 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
type State = {
|
2024-03-15 13:44:17 +00:00
|
|
|
relayInfo: RelayInformation;
|
2023-11-21 07:20:39 +00:00
|
|
|
error: string;
|
|
|
|
isLoading: boolean;
|
|
|
|
};
|
|
|
|
|
|
|
|
type Props = {
|
|
|
|
relayUrl: string;
|
|
|
|
profileGetter: ProfileGetter;
|
2024-03-17 07:07:42 +00:00
|
|
|
emit: emitFunc<SelectConversation>;
|
2023-11-21 07:20:39 +00:00
|
|
|
};
|
|
|
|
|
2024-03-15 13:44:17 +00:00
|
|
|
export class RelayInformationComponent extends Component<Props, State> {
|
2023-11-21 07:20:39 +00:00
|
|
|
styles = {
|
2023-12-18 10:23:15 +00:00
|
|
|
container: `bg-[${SecondaryBackgroundColor}] p-8`,
|
|
|
|
title: `pt-8 text-[${PrimaryTextColor}]`,
|
|
|
|
error: `text-[${ErrorColor}] ${CenterClass}`,
|
2023-11-21 07:20:39 +00:00
|
|
|
header: {
|
2023-12-18 10:23:15 +00:00
|
|
|
container: `text-lg flex text-[${PrimaryTextColor}] pb-4`,
|
2023-11-21 07:20:39 +00:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
state: State = {
|
2024-03-15 13:44:17 +00:00
|
|
|
relayInfo: {
|
2023-11-21 07:20:39 +00:00
|
|
|
name: undefined,
|
|
|
|
description: undefined,
|
|
|
|
pubkey: undefined,
|
|
|
|
contact: undefined,
|
|
|
|
supported_nips: undefined,
|
|
|
|
software: undefined,
|
|
|
|
version: undefined,
|
|
|
|
},
|
|
|
|
error: "",
|
|
|
|
isLoading: false,
|
|
|
|
};
|
|
|
|
|
|
|
|
async componentWillMount() {
|
|
|
|
this.setState({
|
|
|
|
isLoading: true,
|
|
|
|
});
|
2024-03-15 13:44:17 +00:00
|
|
|
const res = await getRelayInformation(this.props.relayUrl);
|
2023-11-21 07:20:39 +00:00
|
|
|
if (res instanceof Error) {
|
|
|
|
this.setState({
|
|
|
|
isLoading: false,
|
|
|
|
error: res.message,
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
this.setState({
|
|
|
|
isLoading: false,
|
2024-03-15 13:44:17 +00:00
|
|
|
relayInfo: res,
|
2023-11-21 07:20:39 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
|
|
|
let vNode;
|
|
|
|
if (this.state.isLoading) {
|
|
|
|
vNode = <Loading />;
|
|
|
|
} else if (this.state.error) {
|
|
|
|
vNode = <p class={this.styles.error}>{this.state.error}</p>;
|
|
|
|
} else {
|
2024-03-17 07:07:42 +00:00
|
|
|
const nodes = [];
|
|
|
|
if (this.state.relayInfo.pubkey) {
|
|
|
|
const pubkey = PublicKey.FromString(this.state.relayInfo.pubkey);
|
|
|
|
if (pubkey instanceof Error) {
|
|
|
|
} else {
|
|
|
|
nodes.push(
|
2023-11-21 07:20:39 +00:00
|
|
|
<Fragment>
|
2024-03-17 07:07:42 +00:00
|
|
|
<p class={this.styles.title}>Admin</p>
|
|
|
|
<AuthorField
|
|
|
|
publicKey={pubkey}
|
|
|
|
profileData={this.props.profileGetter.getProfilesByPublicKey(pubkey)}
|
|
|
|
emit={this.props.emit}
|
|
|
|
/>
|
|
|
|
</Fragment>,
|
2023-11-21 07:20:39 +00:00
|
|
|
);
|
|
|
|
}
|
2024-03-17 07:07:42 +00:00
|
|
|
}
|
|
|
|
if (this.state.relayInfo.contact) {
|
|
|
|
nodes.push(
|
|
|
|
<Fragment>
|
|
|
|
<p class={this.styles.title}>Contact</p>
|
|
|
|
<TextField text={this.state.relayInfo.contact} />
|
|
|
|
</Fragment>,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
if (this.state.relayInfo.description) {
|
|
|
|
nodes.push(
|
|
|
|
<Fragment>
|
|
|
|
<p class={this.styles.title}>Description</p>
|
|
|
|
<TextField text={this.state.relayInfo.description} />
|
|
|
|
</Fragment>,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
if (this.state.relayInfo.software) {
|
|
|
|
nodes.push(
|
|
|
|
<Fragment>
|
|
|
|
<p class={this.styles.title}>Software</p>
|
|
|
|
<TextField text={this.state.relayInfo.software} />
|
|
|
|
</Fragment>,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
if (this.state.relayInfo.version) {
|
|
|
|
nodes.push(
|
|
|
|
<Fragment>
|
|
|
|
<p class={this.styles.title}>Version</p>
|
|
|
|
<TextField text={this.state.relayInfo.version} />
|
|
|
|
</Fragment>,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
if (this.state.relayInfo.supported_nips) {
|
|
|
|
nodes.push(
|
|
|
|
<Fragment>
|
|
|
|
<p class={this.styles.title}>Supported NIPs</p>
|
|
|
|
<TextField text={this.state.relayInfo.supported_nips.join(", ")} />
|
|
|
|
</Fragment>,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
vNode = (
|
|
|
|
<div>
|
|
|
|
{nodes}
|
|
|
|
</div>
|
|
|
|
);
|
2023-11-21 07:20:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div class={this.styles.container}>
|
|
|
|
<p class={this.styles.header.container}>
|
2024-03-17 07:07:42 +00:00
|
|
|
{this.state.relayInfo.icon
|
|
|
|
? (
|
|
|
|
<Avatar
|
|
|
|
class="w-8 h-8 mr-2"
|
|
|
|
picture={this.state.relayInfo.icon || robohash(this.props.relayUrl)}
|
|
|
|
/>
|
|
|
|
)
|
|
|
|
: undefined}
|
|
|
|
{this.state.relayInfo.name}
|
|
|
|
<div class="mx-2"></div>
|
|
|
|
{this.props.relayUrl}
|
2023-11-21 07:20:39 +00:00
|
|
|
</p>
|
|
|
|
{vNode}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function AuthorField(props: {
|
2024-03-17 07:07:42 +00:00
|
|
|
publicKey: PublicKey;
|
|
|
|
profileData: Profile_Nostr_Event | undefined;
|
|
|
|
emit: emitFunc<SelectConversation>;
|
2023-11-21 07:20:39 +00:00
|
|
|
}) {
|
|
|
|
const styles = {
|
2023-12-18 10:23:15 +00:00
|
|
|
avatar: `h-8 w-8 mr-2`,
|
|
|
|
icon: `w-4 h-4 text-[${HintTextColor}] fill-current rotate-180`,
|
|
|
|
name: `overflow-x-auto flex-1`,
|
2023-11-21 07:20:39 +00:00
|
|
|
};
|
|
|
|
|
2024-03-17 07:07:42 +00:00
|
|
|
const { profileData, publicKey } = props;
|
2023-11-21 07:20:39 +00:00
|
|
|
return (
|
|
|
|
<Fragment>
|
2024-03-17 07:07:42 +00:00
|
|
|
<div
|
|
|
|
class={`flex items-center ${InputClass} mt-4 hover:${BackgroundColor_HoverButton} hover:cursor-pointer`}
|
|
|
|
onClick={() => props.emit({ type: "SelectConversation", pubkey: props.publicKey })}
|
|
|
|
>
|
2024-03-15 13:44:17 +00:00
|
|
|
<Avatar
|
2024-03-17 07:07:42 +00:00
|
|
|
picture={profileData?.profile.picture || robohash(publicKey.bech32())}
|
2024-03-15 13:44:17 +00:00
|
|
|
class={styles.avatar}
|
|
|
|
/>
|
2024-03-17 07:07:42 +00:00
|
|
|
<p class={styles.name}>{profileData?.profile.name || publicKey.bech32()}</p>
|
2023-11-21 07:20:39 +00:00
|
|
|
</div>
|
|
|
|
|
2024-03-17 07:07:42 +00:00
|
|
|
<TextField text={publicKey.bech32()} />
|
2023-11-21 07:20:39 +00:00
|
|
|
</Fragment>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function TextField(props: {
|
|
|
|
text: string;
|
|
|
|
}) {
|
|
|
|
const styles = {
|
2023-12-18 10:23:15 +00:00
|
|
|
container: `relative ${InputClass} resize-none flex p-0 mt-4`,
|
|
|
|
pre: `whitespace-pre flex-1 overflow-x-auto px-4 py-3`,
|
|
|
|
copyButton: `w-14 ${CenterClass}`,
|
2023-11-21 07:20:39 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div class={styles.container}>
|
|
|
|
<pre class={styles.pre}>{props.text}</pre>
|
|
|
|
<div class={styles.copyButton}>
|
|
|
|
<CopyButton text={props.text} />
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
2024-03-15 13:44:17 +00:00
|
|
|
|
|
|
|
export async function getRelayInformation(url: string) {
|
|
|
|
try {
|
|
|
|
const httpURL = new URL(url);
|
|
|
|
httpURL.protocol = "https";
|
|
|
|
const res = await fetch(httpURL, {
|
|
|
|
headers: {
|
|
|
|
"accept": "application/nostr+json",
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
if (!res.ok) {
|
|
|
|
return new Error(`Faild to get detail, ${res.status}: ${await res.text()}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
const detail = await res.text();
|
|
|
|
const info = JSON.parse(detail) as RelayInformation;
|
|
|
|
if (!info.icon) {
|
|
|
|
info.icon = robohash(url);
|
|
|
|
}
|
|
|
|
return info;
|
|
|
|
} catch (e) {
|
|
|
|
return e as Error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function robohash(url: string | URL) {
|
|
|
|
return `https://robohash.org/${url}`;
|
|
|
|
}
|