@powerhousedao/academy 5.1.0-dev.9 → 5.1.0-staging.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -36,7 +36,7 @@ Explore the production-ready component structure:
36
36
  git ls-tree -r --name-only tutorial/main editors/chat-room-editor/components/
37
37
 
38
38
  # View a specific component
39
- git show tutorial/main:editors/chat-room-editor/components/ChatRoom.tsx
39
+ git show tutorial/main:editors/chat-room-editor/components/ChatRoom/ChatRoom.tsx
40
40
  ```
41
41
 
42
42
  ### Visual comparison with GitHub Desktop
@@ -91,32 +91,167 @@ We'll build the editor using a component-based approach for better organization
91
91
 
92
92
  ### Component-based architecture
93
93
 
94
- The ChatRoom editor structure includes several components that you can either build yourself or copy from the reference repository:
94
+ The ChatRoom editor uses a modular component structure. Each component has its own folder with an `index.ts` file for clean exports:
95
95
 
96
- - `editor.tsx` - Main editor wrapper (imports ChatRoom)
97
- - `ChatRoom.tsx` - Main container component that orchestrates all other components
98
- - `Message.tsx` - Individual message component with reactions
99
- - `EmojiReaction.tsx` - Emoji reaction UI component
100
- - `TextInput.tsx` - Input component for sending messages
101
- - `Avatar.tsx` - User avatar component
96
+ ```
97
+ editors/chat-room-editor/
98
+ ├── components/
99
+ │ ├── Avatar/ # User avatar display
100
+ │ │ ├── Avatar.tsx
101
+ │ │ └── index.ts
102
+ │ ├── ChatRoom/ # Main chat container
103
+ │ │ ├── ChatRoom.tsx
104
+ │ │ └── index.ts
105
+ │ ├── Header/ # Chat header with editable title/description
106
+ │ │ ├── EditableLabel.tsx
107
+ │ │ ├── Header.tsx
108
+ │ │ └── index.ts
109
+ │ ├── Message/ # Individual message bubble
110
+ │ │ ├── Message.tsx
111
+ │ │ └── index.ts
112
+ │ ├── MessageItem/ # Message with avatar and reaction dropdown
113
+ │ │ ├── MessageItem.tsx
114
+ │ │ └── index.ts
115
+ │ ├── Reaction/ # Emoji reaction display
116
+ │ │ ├── Reaction.tsx
117
+ │ │ └── index.ts
118
+ │ ├── TextInput/ # Message input field
119
+ │ │ ├── SendIcon.tsx
120
+ │ │ ├── TextInput.tsx
121
+ │ │ └── index.ts
122
+ │ └── index.ts # Central exports
123
+ ├── editor.tsx # Main editor component
124
+ ├── utils.ts # Utility functions for data mapping
125
+ └── module.ts # Editor module configuration
126
+ ```
127
+
128
+ ### Copy components from the reference repository
129
+
130
+ Download the repository of the chatroom-demo as a zip file from https://github.com/powerhouse-inc/chatroom-demo and navigate to `editors/chat-room-editor/` to copy the following:
131
+
132
+ 1. **The entire `components/` folder** - Contains all UI components
133
+ 2. **The `utils.ts` file** - Contains utility functions for emoji mapping
102
134
 
103
- ### Option 1: Copy components from the reference
135
+ Here's what each component does:
104
136
 
105
- Download the repository of the chatroom-demo as a zip file from https://github.com/powerhouse-inc/chatroom-demo and navigate to `editors/chat-room-editor` to copy both the `components` folder and `utils.ts` file.
137
+ | Component | Purpose |
138
+ |-----------|---------|
139
+ | `Avatar` | Displays a user avatar image or a deterministic emoji based on the username |
140
+ | `ChatRoom` | Main container that orchestrates the header, messages list, and input field |
141
+ | `Header` | Shows the chat title and description with inline editing capability |
142
+ | `EditableLabel` | Reusable component for inline text editing with edit/cancel icons |
143
+ | `Message` | Renders a single message bubble with styling based on the sender |
144
+ | `MessageItem` | Wraps `Message` with `Avatar` and adds a reaction dropdown menu |
145
+ | `Reaction` | Displays an emoji reaction with a count of users who reacted |
146
+ | `TextInput` | Input field for composing and sending new messages |
147
+
148
+ ### The utils.ts file
149
+
150
+ The `utils.ts` file contains helper functions for mapping between document model types and component props:
151
+
152
+ ```typescript
153
+ import type {
154
+ MessageProps,
155
+ ReactionMap,
156
+ } from "./components/Message/Message.js";
157
+ import type {
158
+ Message,
159
+ ReactionType,
160
+ } from "../../document-models/chat-room/gen/schema/types.js";
161
+
162
+ const emojis = [
163
+ "😀", "😂", "🤣", "😍", "😎", "😊", "🙃", "😇", "🤔", "🥳",
164
+ "🤯", "🤗", "😱", "👻", "🎃", "🐱", "🐶", "🐹", "🦊", "🐻",
165
+ "🐼", "🐨", "🐯", "🦁", "🐸", "🐵", "🐔", "🐧", "🐦", "🐤",
166
+ "🐝", "🐞", "🐟", "🐬", "🐳", "🦋", "🌺", "🌸", "🌼", "🍀",
167
+ ];
168
+
169
+ export function getEmojiFromString(input: string): string {
170
+ function hashString(str: string): number {
171
+ let hash = 0;
172
+ for (let i = 0; i < str.length; i++) {
173
+ const char = str.charCodeAt(i);
174
+ hash = (hash << 5) - hash + char;
175
+ hash |= 0;
176
+ }
177
+ return Math.abs(hash);
178
+ }
106
179
 
107
- In this folder you'll find:
108
- - An avatar component for chat room participants
109
- - The chatroom environment itself
110
- - A header for the chatroom
111
- - The UI for rendering messages, usernames, and reaction popups
112
- - The emoji reaction interface
113
- - A text input field component
180
+ const hash = hashString(input);
181
+ return emojis[hash % emojis.length];
182
+ }
114
183
 
115
- The utils function will help you with mapping information from the document model to your chatroom components, such as mapping emoji values to the relevant emoji to be displayed.
184
+ export const reactionTypeToEmoji = (reactionType: ReactionType): string => {
185
+ switch (reactionType) {
186
+ case "HEART":
187
+ return "❤️";
188
+ case "THUMBS_UP":
189
+ return "👍";
190
+ case "THUMBS_DOWN":
191
+ return "👎";
192
+ case "LAUGH":
193
+ return "😂";
194
+ case "CRY":
195
+ return "😢";
196
+ default:
197
+ return "❤️";
198
+ }
199
+ };
200
+
201
+ export const reactionTypeToReactionKey = (
202
+ reactionType: ReactionType,
203
+ ): keyof ReactionMap => {
204
+ switch (reactionType) {
205
+ case "HEART":
206
+ return "heart";
207
+ case "THUMBS_UP":
208
+ return "thumbsUp";
209
+ case "THUMBS_DOWN":
210
+ return "thumbsDown";
211
+ case "LAUGH":
212
+ return "laughing";
213
+ case "CRY":
214
+ return "cry";
215
+ default:
216
+ return "heart";
217
+ }
218
+ };
219
+
220
+ export const reactionKeyToReactionType = (
221
+ reactionKey: string,
222
+ ): ReactionType => {
223
+ switch (reactionKey) {
224
+ case "heart":
225
+ return "HEART";
226
+ case "thumbsUp":
227
+ return "THUMBS_UP";
228
+ case "thumbsDown":
229
+ return "THUMBS_DOWN";
230
+ case "laughing":
231
+ return "LAUGH";
232
+ case "cry":
233
+ return "CRY";
234
+ default:
235
+ return "HEART";
236
+ }
237
+ };
238
+
239
+ export const mapReactions = (
240
+ reactions: Message["reactions"],
241
+ ): MessageProps["reactions"] => {
242
+ return (reactions || [])
243
+ .map((reaction) => ({
244
+ emoji: reactionTypeToEmoji(reaction.type),
245
+ reactedBy: reaction.reactedBy,
246
+ type: reactionTypeToReactionKey(reaction.type),
247
+ }))
248
+ .filter((reaction) => reaction.reactedBy.length > 0);
249
+ };
250
+ ```
116
251
 
117
- ### Option 2: Build the main editor file
252
+ ### The main editor.tsx file
118
253
 
119
- If you want to understand how everything connects, here's the main `editor.tsx` implementation:
254
+ The main `editor.tsx` file connects your document model to the UI components. Replace the generated scaffolding with:
120
255
 
121
256
  ```typescript
122
257
  import { generateId } from "document-model/core";
@@ -146,7 +281,6 @@ export default function Editor() {
146
281
  return <div>Loading...</div>;
147
282
  }
148
283
 
149
- // Map document messages to component props
150
284
  const messages: ChatRoomProps["messages"] =
151
285
  document.state.global.messages.map((message) => ({
152
286
  id: message.id,
@@ -158,9 +292,10 @@ export default function Editor() {
158
292
  reactions: mapReactions(message.reactions),
159
293
  }));
160
294
 
161
- // Handler for sending messages
162
295
  const onSendMessage: ChatRoomProps["onSendMessage"] = (message) => {
163
- if (!message) return;
296
+ if (!message) {
297
+ return;
298
+ }
164
299
 
165
300
  dispatch(
166
301
  addMessage({
@@ -176,7 +311,6 @@ export default function Editor() {
176
311
  );
177
312
  };
178
313
 
179
- // Handler for adding reactions
180
314
  const addReaction = (
181
315
  messageId: string,
182
316
  userId: string,
@@ -191,7 +325,6 @@ export default function Editor() {
191
325
  );
192
326
  };
193
327
 
194
- // Handler for removing reactions
195
328
  const removeReaction = (
196
329
  messageId: string,
197
330
  userId: string,
@@ -206,13 +339,14 @@ export default function Editor() {
206
339
  );
207
340
  };
208
341
 
209
- // Handler for clicking on reactions (toggle behavior)
210
342
  const onClickReaction: MessageProps["onClickReaction"] = (reaction) => {
211
343
  const message = messages.find(
212
344
  (message) => message.id === reaction.messageId,
213
345
  );
214
346
 
215
- if (!message) return;
347
+ if (!message) {
348
+ return;
349
+ }
216
350
 
217
351
  const messageId = reaction.messageId;
218
352
  const reactionType = reactionKeyToReactionType(reaction.type);
@@ -233,7 +367,6 @@ export default function Editor() {
233
367
  }
234
368
  };
235
369
 
236
- // Handlers for editing chat metadata
237
370
  const onSubmitTitle: ChatRoomProps["onSubmitTitle"] = (title) => {
238
371
  dispatch(editChatName({ name: title }));
239
372
  };
@@ -245,7 +378,11 @@ export default function Editor() {
245
378
  };
246
379
 
247
380
  return (
248
- <div style={{ height: "calc(100vh - 140px)" }}>
381
+ <div
382
+ style={{
383
+ height: "calc(100vh - 140px)",
384
+ }}
385
+ >
249
386
  <ChatRoom
250
387
  description={
251
388
  document.state.global.description || "This is a chat room demo"
@@ -279,6 +416,91 @@ The `useSelectedChatRoomDocument` hook is generated by the Powerhouse CLI. It pr
279
416
  This hook connects your React components to the document model's state and operations.
280
417
  :::
281
418
 
419
+ ## Key components explained
420
+
421
+ ### MessageItem component
422
+
423
+ The `MessageItem` component wraps the `Message` component with an avatar and a reaction dropdown menu. It uses the `@powerhousedao/design-system` package for the dropdown:
424
+
425
+ ```typescript
426
+ import { Message, type MessageProps } from "../Message/Message.js";
427
+ import { Avatar, type AvatarProps } from "../Avatar/Avatar.js";
428
+ import { useState } from "react";
429
+ import {
430
+ DropdownMenu,
431
+ DropdownMenuContent,
432
+ DropdownMenuTrigger,
433
+ DropdownMenuItem,
434
+ } from "@powerhousedao/design-system";
435
+
436
+ export type MessageItemProps = MessageProps & AvatarProps;
437
+
438
+ export const reactionMap = {
439
+ cry: "😢",
440
+ laughing: "😂",
441
+ heart: "❤️",
442
+ thumbsDown: "👎",
443
+ thumbsUp: "👍",
444
+ };
445
+
446
+ export const MessageItem: React.FC<MessageItemProps> = (props) => {
447
+ const { imgUrl, userName, isCurrentUser, ...messageProps } = props;
448
+ const { disabled = false } = messageProps;
449
+
450
+ const [isHovered, setIsHovered] = useState(false);
451
+ const [open, setOpen] = useState(false);
452
+
453
+ // ... hover and dropdown logic
454
+
455
+ return (
456
+ <div style={{ display: "flex", gap: "2px", alignItems: "flex-end" }}>
457
+ <Avatar imgUrl={imgUrl} userName={userName} />
458
+ <DropdownMenu>
459
+ <Message isCurrentUser={isCurrentUser} userName={userName} {...messageProps} />
460
+ <DropdownMenuTrigger>🫥</DropdownMenuTrigger>
461
+ <DropdownMenuContent>
462
+ {Object.entries(reactionMap).map(([key, emoji]) => (
463
+ <DropdownMenuItem key={key} onClick={() => /* handle reaction */}>
464
+ {emoji}
465
+ </DropdownMenuItem>
466
+ ))}
467
+ </DropdownMenuContent>
468
+ </DropdownMenu>
469
+ </div>
470
+ );
471
+ };
472
+ ```
473
+
474
+ ### EditableLabel component
475
+
476
+ The `EditableLabel` component enables inline editing of text fields (like the chat title and description):
477
+
478
+ ```typescript
479
+ export const EditableLabel: React.FC<EditableLabelProps> = ({
480
+ label: initialLabel,
481
+ onSubmit,
482
+ style,
483
+ }) => {
484
+ const [hover, setHover] = useState(false);
485
+ const [isEditing, setIsEditing] = useState(false);
486
+ const [label, setLabel] = useState(initialLabel);
487
+
488
+ // Toggle between read mode (displaying text) and write mode (input field)
489
+ // Press Enter to submit, Escape to cancel
490
+
491
+ return (
492
+ <div onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}>
493
+ {isEditing ? (
494
+ <input value={label} onChange={(e) => setLabel(e.target.value)} />
495
+ ) : (
496
+ <h1>{label}</h1>
497
+ )}
498
+ {(hover || isEditing) && <EditIcon onClick={() => setIsEditing(true)} />}
499
+ </div>
500
+ );
501
+ };
502
+ ```
503
+
282
504
  ## Test your editor
283
505
 
284
506
  Now you can run the Connect app and see the **ChatRoom** editor in action:
@@ -309,29 +531,15 @@ If you managed to follow this tutorial until this point, you have successfully i
309
531
 
310
532
  The tutorial repository includes the complete ChatRoom editor with all components:
311
533
 
312
- ```
313
- editors/chat-room-editor/
314
- ├── components/
315
- │ ├── Avatar.tsx # User avatar display
316
- │ ├── ChatRoom.tsx # Main chat container
317
- │ ├── Header.tsx # Chat header with title/description
318
- │ ├── Message.tsx # Individual message display
319
- │ ├── EmojiReaction.tsx # Reaction UI
320
- │ ├── TextInput.tsx # Message input field
321
- │ └── index.ts # Component exports
322
- ├── editor.tsx # Main editor component
323
- ├── utils.ts # Utility functions for data mapping
324
- └── module.ts # Editor module configuration
325
- ```
326
-
327
- **View individual components from the reference:**
328
-
329
534
  ```bash
330
535
  # See the ChatRoom component implementation
331
- git show tutorial/main:editors/chat-room-editor/components/ChatRoom.tsx
536
+ git show tutorial/main:editors/chat-room-editor/components/ChatRoom/ChatRoom.tsx
537
+
538
+ # Explore the MessageItem component
539
+ git show tutorial/main:editors/chat-room-editor/components/MessageItem/MessageItem.tsx
332
540
 
333
- # Explore the Message component
334
- git show tutorial/main:editors/chat-room-editor/components/Message.tsx
541
+ # View the EditableLabel component
542
+ git show tutorial/main:editors/chat-room-editor/components/Header/EditableLabel.tsx
335
543
 
336
544
  # Compare your implementation with the reference
337
545
  git diff tutorial/main -- editors/chat-room-editor/
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@powerhousedao/academy",
3
- "version": "5.1.0-dev.9",
3
+ "version": "5.1.0-staging.0",
4
4
  "homepage": "https://powerhouse.academy",
5
5
  "repository": {
6
6
  "type": "git",
package/sidebars.ts CHANGED
@@ -41,7 +41,6 @@ const sidebars = {
41
41
  items: [
42
42
  "academy/MasteryTrack/BuilderEnvironment/Prerequisites",
43
43
  "academy/MasteryTrack/BuilderEnvironment/StandardDocumentModelWorkflow",
44
- "academy/MasteryTrack/BuilderEnvironment/BuilderTools",
45
44
  "academy/MasteryTrack/BuilderEnvironment/VetraStudio",
46
45
  ],
47
46
  },