blowater/UI/dm.tsx

287 lines
11 KiB
TypeScript
Raw Normal View History

2023-06-30 14:05:57 +00:00
/** @jsx h */
import { Component, h, VNode } from "https://esm.sh/preact@10.17.1";
2023-09-23 21:54:13 +00:00
import * as cl from "./conversation-list.tsx";
2023-12-22 07:51:08 +00:00
import { MessagePanel, NewMessageListener } from "./message-panel.tsx";
2023-10-04 21:34:43 +00:00
import { EventBus } from "../event-bus.ts";
2023-10-04 15:07:40 +00:00
import { CenterClass, IconButtonClass } from "./components/tw.ts";
2023-12-05 17:30:58 +00:00
import { ChatMessagesGetter, UI_Interaction_Event } from "./app_update.tsx";
2023-10-09 18:52:30 +00:00
import { NostrAccountContext, NostrEvent } from "../lib/nostr-ts/nostr.ts";
2023-11-03 13:09:13 +00:00
import { ConnectionPool } from "../lib/nostr-ts/relay-pool.ts";
import { ProfileSyncer } from "../features/profile.ts";
2023-06-30 14:05:57 +00:00
import { getFocusedContent } from "./app.tsx";
import { EventSyncer } from "./event_syncer.ts";
2023-10-04 15:07:40 +00:00
import { PrimaryTextColor } from "./style/colors.ts";
2023-11-11 11:19:21 +00:00
import { SettingIcon } from "./icons/setting-icon.tsx";
import { GroupMessageController } from "../features/gm.ts";
import { ProfileGetter } from "./search.tsx";
2023-10-07 17:15:27 +00:00
import { PublicKey } from "../lib/nostr-ts/key.ts";
2023-10-09 18:52:30 +00:00
import { EditorModel } from "./editor.tsx";
2023-10-19 03:44:43 +00:00
import { InviteButton } from "./invite-button.tsx";
2023-10-20 04:16:55 +00:00
import { IS_BETA_VERSION } from "./config.js";
2023-11-11 11:19:21 +00:00
import { UserIcon } from "./icons/user-icon.tsx";
import { LeftArrowIcon } from "./icons/left-arrow-icon.tsx";
2023-11-23 04:28:45 +00:00
import { RelayRecordGetter } from "../database.ts";
2023-12-22 07:51:08 +00:00
import { RightPanelModel } from "./right-panel.tsx";
import { Channel, PopChannel } from "https://raw.githubusercontent.com/BlowaterNostr/csp/master/csp.ts";
2023-10-09 18:52:30 +00:00
export type DM_Model = {
currentEditor: EditorModel | undefined;
2023-10-15 22:39:21 +00:00
focusedContent: Map<string, NostrEvent | PublicKey>;
2023-10-09 18:52:30 +00:00
isGroupMessage: boolean;
};
2023-06-30 14:05:57 +00:00
type DirectMessageContainerProps = {
rightPanelModel: RightPanelModel;
2023-10-04 21:34:43 +00:00
ctx: NostrAccountContext;
2023-06-30 14:05:57 +00:00
pool: ConnectionPool;
2023-10-04 21:34:43 +00:00
bus: EventBus<UI_Interaction_Event>;
2023-10-01 20:59:07 +00:00
profilesSyncer: ProfileSyncer;
eventSyncer: EventSyncer;
groupChatController: GroupMessageController;
// getters
profileGetter: ProfileGetter;
messageGetter: ChatMessagesGetter;
newMessageListener: NewMessageListener;
pinListGetter: cl.PinListGetter;
conversationLists: cl.ConversationListRetriever;
2023-10-07 20:40:18 +00:00
newMessageChecker: cl.NewMessageChecker;
2023-11-23 04:28:45 +00:00
relayRecordGetter: RelayRecordGetter;
} & DM_Model;
2023-06-30 14:05:57 +00:00
2023-10-07 17:15:27 +00:00
export type StartInvite = {
type: "StartInvite";
publicKey: PublicKey;
};
type State = {
currentEditor: EditorModel | undefined;
};
2023-06-30 14:05:57 +00:00
export class DirectMessageContainer extends Component<DirectMessageContainerProps, State> {
changes?: PopChannel<UI_Interaction_Event>;
state: State = {
currentEditor: undefined,
};
componentWillUpdate(nextProps: Readonly<DirectMessageContainerProps>): void {
this.setState({
currentEditor: nextProps.currentEditor,
});
}
async componentDidMount() {
this.setState({
currentEditor: this.props.currentEditor,
});
const changes = this.props.bus.onChange();
this.changes = changes;
for await (const change of changes) {
if (change.type == "SelectConversation") {
// todo
}
}
}
componentWillUnmount(): void {
if (this.changes) {
this.changes.close();
}
}
2023-10-24 09:03:12 +00:00
render(props: DirectMessageContainerProps) {
const t = Date.now();
const currentEditor = props.currentEditor;
let buttons = [];
if (currentEditor && IS_BETA_VERSION) {
if (props.isGroupMessage) {
buttons.push(
2023-10-19 03:44:43 +00:00
<button
2023-12-18 10:23:15 +00:00
class={`w-8 h-8 ${CenterClass}`}
2023-10-19 03:44:43 +00:00
onClick={() => {
props.bus.emit({
type: "ViewUserDetail",
pubkey: currentEditor.pubkey,
2023-10-19 03:44:43 +00:00
});
}}
>
<UserIcon
2023-12-18 10:23:15 +00:00
class={`w-6 h-6 text-[${PrimaryTextColor}] stroke-current`}
2023-10-19 03:44:43 +00:00
style={{ fill: "none" }}
/>
</button>,
);
const canEditGroupProfile = props.groupChatController.getGroupAdminCtx(currentEditor.pubkey);
if (canEditGroupProfile) {
buttons.push(
// setting button
<button
class={`w-8 h-8 ${CenterClass}`}
onClick={() => {
props.bus.emit({
type: "StartEditGroupChatProfile",
ctx: canEditGroupProfile,
});
}}
>
<SettingIcon
class={`w-6 h-6 text-[${PrimaryTextColor}] stroke-current`}
style={{ fill: "none" }}
/>
</button>,
);
}
} else {
buttons.push(
<InviteButton
groupChatController={props.groupChatController}
profileGetter={props.profileGetter}
userPublicKey={currentEditor.pubkey}
emit={props.bus.emit}
/>,
);
}
}
const vDom = (
<div
class={`h-full w-full flex bg-[#36393F] overflow-hidden`}
>
<div
class={`w-fit
max-sm:w-full
${props.currentEditor ? "max-sm:hidden" : ""}`}
>
<cl.ConversationList
eventBus={props.bus}
emit={props.bus.emit}
convoListRetriever={props.conversationLists}
groupChatListGetter={props.groupChatController}
hasNewMessages={props.newMessageChecker}
{...props}
/>
</div>
{this.state.currentEditor
? (
<div class={`flex flex-col flex-1 overflow-hidden`}>
<TopBar
bus={this.props.bus}
buttons={buttons}
currentEditor={this.state.currentEditor}
profileGetter={this.props.profileGetter}
showRightPanel={this.props.rightPanelModel.show}
/>
<div class={`flex-1 overflow-auto`}>
<MessagePanel
myPublicKey={props.ctx.publicKey}
rightPanelModel={props.rightPanelModel}
emit={props.bus.emit}
newMessageListener={props.newMessageListener}
focusedContent={getFocusedContent(
props.focusedContent.get(this.state.currentEditor.pubkey.hex),
props.profileGetter,
)}
profilesSyncer={props.profilesSyncer}
eventSyncer={props.eventSyncer}
isGroupMessage={props.isGroupMessage}
profileGetter={props.profileGetter}
editorModel={this.state.currentEditor}
messageGetter={props.messageGetter}
relayRecordGetter={props.relayRecordGetter}
/>
</div>
</div>
)
: undefined}
</div>
);
console.debug("DirectMessageContainer:end", Date.now() - t);
return vDom;
2023-10-19 03:44:43 +00:00
}
}
2023-10-19 03:44:43 +00:00
function TopBar(props: {
bus: EventBus<UI_Interaction_Event>;
currentEditor: EditorModel;
profileGetter: ProfileGetter;
showRightPanel: boolean;
buttons: VNode[];
}) {
return (
2023-06-30 14:05:57 +00:00
<div
class={`h-14 border-l border-b border-[#36393F] flex
items-center justify-between bg-[#2F3136]`}
2023-06-30 14:05:57 +00:00
>
<div class={`flex items-center overflow-hidden`}>
<button
onClick={() => {
props.bus.emit({
type: "BackToContactList",
});
}}
class={`w-6 h-6 mobile:mr-2 desktop:hidden ${IconButtonClass}`}
>
<LeftArrowIcon
class={`w-4 h-4`}
style={{
fill: "rgb(185, 187, 190)",
}}
/>
</button>
<span
// https://tailwindcss.com/docs/customizing-colors
// https://tailwindcss.com/docs/cursor
class={`text-[#F3F4EA] text-[1.2rem]
2023-12-05 17:30:58 +00:00
hover:text-[#60a5fa] hover:cursor-pointer
ml-4 mobile:text-base whitespace-nowrap truncate`}
onClick={() => {
if (!props.currentEditor) {
return;
}
props.bus.emit({
type: "ViewUserDetail",
pubkey: props.currentEditor.pubkey,
});
}}
>
{props.profileGetter.getProfilesByPublicKey(
props.currentEditor.pubkey,
)
?.profile.name ||
props.currentEditor.pubkey.bech32()}
</span>
</div>
<div>
{props.buttons}
{!props.showRightPanel
? (
<button
class={`absolute z-10 w-6 h-6 transition-transform duration-100 ease-in-out right-4 mobile:right-0 top-4${
props.showRightPanel ? " rotate-180" : ""
} ${IconButtonClass}`}
onClick={() => {
props.bus.emit({
type: "ToggleRightPanel",
show: !props.showRightPanel,
});
}}
>
<LeftArrowIcon
class={`w-4 h-4`}
style={{
fill: "#F3F4EA",
}}
/>
</button>
)
: undefined}
</div>
2023-06-30 14:05:57 +00:00
</div>
);
}