import { action, computed, Default, equals, handler, NAME, pattern, type PerSession, type PerSpace, type PerUser, Stream, UI, Writable, } from "commonfabric"; import { createRandomImportedClaimedMessages, seededRandom, sortDisplayMessages, } from "./logic.ts"; import { chatAdminEveryoneIsAdmin, type ChatAdminRegistryCell, type ChatAdminRegistryValue, commitTrustedMessageSend, currentProfileCell, currentUserCanManageAdmins, currentUserIsAdmin as currentProfileIsAdmin, messagesValue, type MyProfileCell, participantClaimsValue, type RoomDraftCell, roomsValue, type SharedChatMessage, type SharedMessagesCell, type SharedMessagesValue, type SharedProfilesCell, type SharedProfilesValue, type SharedRoomsCell, type SharedRoomsValue, TrustedAdminPanel, type TrustedAdminPolicyEvent, TrustedChatSendSurface, TrustedProfileSaveSurface, TrustedRoomAddSurface, } from "./trusted.tsx"; type DraftCell = Writable>; const messageCountText = (count: number): string => count === 0 ? "No messages yet" : `${count} message${count === 1 ? "" : "s"}`; const roomCountText = (count: number): string => count === 0 ? "No rooms yet" : `${count} room${count === 1 ? "" : "s"}`; const draftText = (draft: DraftCell): string => (draft.get() as string | undefined) ?? ""; const writeDraftText = handler( (nextValue, { value }) => { value.set(nextValue); }, ); interface SharedTranscriptInput { myProfile: MyProfileCell; messages: SharedMessagesCell; id: string; } const SharedTranscript = pattern< SharedTranscriptInput, { [NAME]: string; [UI]: any } >(( { myProfile, messages, id }: SharedTranscriptInput, ): { [NAME]: string; [UI]: any } => { const messageCountLabel = computed(() => messageCountText(messagesValue(messages).length) ); const transcriptRows = messages.map((messageCell) => { const authorProfile = messageCell.authorProfile; const isMine = computed(() => equals(currentProfileCell(myProfile), authorProfile) ); return (
{messageCell.authorName} {messageCell.body}
); }); return { [NAME]: computed(() => `${id} transcript`), [UI]: ( {messageCountLabel}
{transcriptRows}
), }; }); type SharedTranscriptInputArg = Parameters[0]; interface RoomsListInput { rooms: SharedRoomsCell; id: string; } const RoomsList = pattern(( { rooms, id }: RoomsListInput, ): { [NAME]: string; [UI]: any } => { const roomListText = computed(() => { const names = roomsValue(rooms).map((room) => room.name); return names.length === 0 ? "No rooms yet" : names.join(" ยท "); }); return { [NAME]: computed(() => `${id} rooms`), [UI]: ( ), }; }); type RoomsListInputArg = Parameters[0]; type TrustedChatSendSurfaceInputArg = Parameters< typeof TrustedChatSendSurface >[0]; type TrustedProfileSaveSurfaceInputArg = Parameters< typeof TrustedProfileSaveSurface >[0]; type TrustedAdminPanelInputArg = Parameters[0]; type TrustedRoomAddSurfaceInputArg = Parameters< typeof TrustedRoomAddSurface >[0]; type TrustedMessageSendInputArg = Parameters[ 0 ]; export interface GroupChatDemoInput { myProfile?: PerUser; profiles?: PerSpace; messages?: PerSpace; rooms?: PerSpace; adminRegistry?: PerSpace; profileDraft?: PerUser; messageDraft?: PerUser; hostMessageDraft?: PerSession; roomDraft?: PerSession; } export interface GroupChatDemoOutput { [NAME]: string; [UI]: any; myProfile: PerUser; profiles: PerSpace; messages: PerSpace; rooms: PerSpace; adminRegistry: PerSpace; profileDraft: PerUser; messageDraft: PerUser; hostMessageDraft: PerSession; roomDraft: PerSession; setProfileDraft: Stream; setMessageDraft: Stream; setHostMessageDraft: Stream; setRoomDraft: Stream; currentProfileName: string; currentUserIsAdmin: boolean; currentUserCanManageAdmins: boolean; saveProfile: Stream; toggleCurrentUserAdmin: Stream; toggleParticipantAdmin: Stream; toggleEveryoneAdmin: Stream; sendTrustedMessage: Stream; addTrustedRoom: Stream; hostLookalikeSend: Stream; addRandomMessages: Stream; } export const GroupChatDemo = pattern(( { myProfile, profiles, messages, rooms, adminRegistry, profileDraft, messageDraft, hostMessageDraft, roomDraft, }: GroupChatDemoInput, ): GroupChatDemoOutput => { const myProfileCell = myProfile as MyProfileCell; const profilesCell = profiles as SharedProfilesCell; const messagesCell = messages as SharedMessagesCell; const roomsCell = rooms as SharedRoomsCell; const adminRegistryCell = adminRegistry as ChatAdminRegistryCell; const profileDraftCell = profileDraft as DraftCell; const messageDraftCell = messageDraft as DraftCell; const hostMessageDraftCell = hostMessageDraft as DraftCell; const roomDraftCell = roomDraft as RoomDraftCell; const trustedProfileSave = TrustedProfileSaveSurface({ myProfile: myProfileCell, profiles: profilesCell, nameDraft: profileDraftCell, } as TrustedProfileSaveSurfaceInputArg); const trustedAdminPanel = TrustedAdminPanel({ profiles: profilesCell, myProfile: myProfileCell, messages: messagesCell, adminRegistry: adminRegistryCell, } as TrustedAdminPanelInputArg); const trustedSend = TrustedChatSendSurface({ profiles: profilesCell, myProfile: myProfileCell, messageDraft: messageDraftCell, messages: messagesCell, } as TrustedChatSendSurfaceInputArg); const trustedRoomAdd = TrustedRoomAddSurface({ myProfile: myProfileCell, adminRegistry: adminRegistryCell, roomDraft: roomDraftCell, rooms: roomsCell, } as TrustedRoomAddSurfaceInputArg); const hostLookalikeSend = commitTrustedMessageSend({ myProfile: myProfileCell, messageDraft: hostMessageDraftCell, messages: messagesCell, } as TrustedMessageSendInputArg); const setProfileDraft = writeDraftText({ value: profileDraftCell }); const setMessageDraft = writeDraftText({ value: messageDraftCell }); const setHostMessageDraft = writeDraftText({ value: hostMessageDraftCell }); const setRoomDraft = writeDraftText({ value: roomDraftCell }); const participantCountLabel = computed(() => { const count = participantClaimsValue(profilesCell, myProfileCell, messagesCell).length; return `${count} participant${count === 1 ? "" : "s"}`; }); const roomCountLabel = computed(() => roomCountText(roomsValue(roomsCell).length) ); const hostSendDisabled = computed(() => draftText(hostMessageDraftCell).trim().length === 0 ); const addRandomMessagesDisabled = computed(() => participantClaimsValue(profilesCell, myProfileCell, messagesCell).length === 0 || messagesValue(messagesCell).length === 0 ); const addRandomMessages = action(() => { const nextMessages = createRandomImportedClaimedMessages( sortDisplayMessages(messagesValue(messagesCell)), participantClaimsValue(profilesCell, myProfileCell, messagesCell), seededRandom(0xdecafbad), ); nextMessages.forEach((message) => messagesCell.push(message as SharedChatMessage) ); }); return { [NAME]: "CFC group chat demo", [UI]: ( Group chat Ordinary chat layout built from small reviewed CFC surfaces. messageCountText(messagesValue(messagesCell).length) )} /> currentProfileIsAdmin(myProfileCell, adminRegistryCell) ? "Admin enabled" : "Admin off" )} /> currentProfileCell(myProfileCell) === undefined ? "No profile" : chatAdminEveryoneIsAdmin(adminRegistryCell) ? "Everyone is admin" : currentUserCanManageAdmins( myProfileCell, adminRegistryCell, ) ? "Can manage admins" : "Manager off" )} /> {trustedProfileSave} {trustedAdminPanel} Rooms {roomCountLabel} {RoomsList({ rooms: roomsCell, id: "trusted-room-list", } as RoomsListInputArg)} {trustedRoomAdd} {SharedTranscript({ myProfile: myProfileCell, messages: messagesCell, id: "trusted-conversation-preview", } as SharedTranscriptInputArg)} {trustedSend} Send Insert fake messages ), myProfile: myProfileCell as PerUser, profiles: profilesCell as PerSpace, messages: messagesCell as PerSpace, rooms: roomsCell as PerSpace, adminRegistry: adminRegistryCell as PerSpace, profileDraft: profileDraftCell as PerUser, messageDraft: messageDraftCell as PerUser, hostMessageDraft: hostMessageDraftCell as PerSession, roomDraft: roomDraftCell as PerSession, setProfileDraft, setMessageDraft, setHostMessageDraft, setRoomDraft, currentProfileName: trustedProfileSave.currentProfileName, currentUserIsAdmin: computed(() => currentProfileIsAdmin(myProfileCell, adminRegistryCell) ), currentUserCanManageAdmins: computed(() => currentUserCanManageAdmins(myProfileCell, adminRegistryCell) ), saveProfile: trustedProfileSave.saveProfile, toggleCurrentUserAdmin: trustedAdminPanel.toggleCurrentUserAdmin, toggleParticipantAdmin: trustedAdminPanel.toggleParticipantAdmin, toggleEveryoneAdmin: trustedAdminPanel.toggleEveryoneAdmin, sendTrustedMessage: trustedSend.sendMessage, addTrustedRoom: trustedRoomAdd.addRoom, hostLookalikeSend, addRandomMessages, }; }); export default GroupChatDemo; export type { ChatAdminRegistryValue, SharedMessagesValue, SharedProfilesValue, SharedRoomsValue, };