@dao42/d42paas-front 0.4.5 → 0.4.6

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.
Files changed (66) hide show
  1. package/package.json +3 -2
  2. package/src/app.tsx +250 -0
  3. package/src/assets/code-brackets.svg +1 -0
  4. package/src/assets/colors.svg +1 -0
  5. package/src/assets/comments.svg +1 -0
  6. package/src/assets/direction.svg +1 -0
  7. package/src/assets/flow.svg +1 -0
  8. package/src/assets/plugin.svg +1 -0
  9. package/src/assets/repo.svg +1 -0
  10. package/src/assets/stackalt.svg +1 -0
  11. package/src/components/Avatar/index.tsx +27 -0
  12. package/src/components/CanvasHelper/index.tsx +89 -0
  13. package/src/components/Console/index.tsx +88 -0
  14. package/src/components/Editor/index.tsx +979 -0
  15. package/src/components/FileTree/index.tsx +477 -0
  16. package/src/components/LiveContent/index.tsx +221 -0
  17. package/src/components/LiveContent/video.tsx +213 -0
  18. package/src/components/LottieAnim/index.tsx +41 -0
  19. package/src/components/Message/index.tsx +64 -0
  20. package/src/components/Model/index.tsx +42 -0
  21. package/src/components/OutputBrowser/index.tsx +180 -0
  22. package/src/components/Skeleton/index.tsx +41 -0
  23. package/src/components/Tabs/index.tsx +23 -0
  24. package/src/components/Terminal/index.tsx +127 -0
  25. package/src/components/ToolBar/index.tsx +169 -0
  26. package/src/components/XTerm/index.tsx +113 -0
  27. package/src/components/index.tsx +4 -0
  28. package/src/components/loading/index.tsx +282 -0
  29. package/src/enum/FExtension.ts +168 -0
  30. package/src/helpers/collections/IoClient.tsx +314 -0
  31. package/src/helpers/collections/errorCatcher.tsx +0 -0
  32. package/src/helpers/collections/idb.tsx +186 -0
  33. package/src/helpers/collections/localStorage.tsx +13 -0
  34. package/src/helpers/collections/mock.tsx +30 -0
  35. package/src/helpers/collections/playgroundInit.tsx +311 -0
  36. package/src/helpers/collections/replay.tsx +168 -0
  37. package/src/helpers/collections/socket.tsx +6 -0
  38. package/src/helpers/collections/toast.tsx +19 -0
  39. package/src/helpers/collections/userTool.tsx +12 -0
  40. package/src/helpers/collections/util.tsx +4 -0
  41. package/src/helpers/index.tsx +6 -0
  42. package/src/helpers/monaco/monaco-ot-adapter.tsx +476 -0
  43. package/src/hooks/collections/useOT.tsx +38 -0
  44. package/src/hooks/index.tsx +1 -0
  45. package/src/pages/index.tsx +450 -0
  46. package/src/public/dev.html +35 -0
  47. package/src/public/index.html +45 -0
  48. package/src/public/sdkserver.html +35 -0
  49. package/src/stores/index.tsx +1 -0
  50. package/src/stores/oTStore.tsx +288 -0
  51. package/src/stories/BrowserWindow.tsx +30 -0
  52. package/src/stories/Console.tsx +46 -0
  53. package/src/stories/Editor.tsx +37 -0
  54. package/src/stories/FileTree.tsx +50 -0
  55. package/src/stories/Shell.tsx +53 -0
  56. package/src/stories/introduction.stories.mdx +193 -0
  57. package/src/stories/page.tsx +71 -0
  58. package/src/styles/collections/iconfont.scss +1 -0
  59. package/src/styles/collections/tabs-costumers.scss +20 -0
  60. package/src/styles/collections/tailwind.scss +3 -0
  61. package/src/styles/collections/tree-costumers.scss +53 -0
  62. package/src/styles/collections/utility.scss +10 -0
  63. package/src/styles/collections/xterm-costumers.scss +47 -0
  64. package/src/styles/index.scss +19 -0
  65. package/src/types/editor.d.ts +31 -0
  66. package/src/types/index.d.ts +158 -0
@@ -0,0 +1,477 @@
1
+ import React, { useEffect, useRef, useState } from 'react';
2
+
3
+ import { Tree, ControlledTreeEnvironment } from 'react-complex-tree';
4
+ import { TreeItem, TreeItemIndex } from 'react-complex-tree/lib/esm/types';
5
+ import { oTStore } from '~/stores';
6
+ import { useOT } from '~/hooks';
7
+ import { IsMe } from '~/helpers';
8
+ import { userListStore, userStore } from '~/stores/oTStore';
9
+
10
+ export type FileSystemData = {
11
+ type: 'DIRECTORY' | 'FILE';
12
+ name: string;
13
+ children: FileSystemData[];
14
+ };
15
+
16
+ export type UploadFile = {
17
+ path: string;
18
+ content: string;
19
+ };
20
+
21
+ export type AddedFiles = {
22
+ target: string;
23
+ files: UploadFile[];
24
+ };
25
+
26
+ interface FileTreeProps {
27
+ onCustomSelect?: (items: TreeItemIndex[], treeId?: string) => void | null;
28
+ onCustomExpand?: (fileData: AddedFiles) => void | null;
29
+ onCustomFocus?: (fileData: AddedFiles) => void | null;
30
+ onCustomCollapse?: (fileData: AddedFiles) => void | null;
31
+ onCustomUpload?: (fileData: AddedFiles) => void | null;
32
+ }
33
+
34
+ function addChildrenArray(children, parent) {
35
+ for (let i = 0; i < children.length; i++) {
36
+ if (children[i].type === 'DIRECTORY') {
37
+ parent.children.push(`${parent.index}${children[i].name}/`);
38
+ } else {
39
+ parent.children.push(`${parent.index}${children[i].name}`);
40
+ }
41
+ }
42
+ }
43
+
44
+ function formatFileToTree(
45
+ data: FileSystemData,
46
+ ): Record<TreeItemIndex, TreeItem<string>> {
47
+ if (!data) return;
48
+ const formatData: Record<TreeItemIndex, TreeItem<string>> = {};
49
+ const root = data.name;
50
+
51
+ formatData[root] = {
52
+ index: root,
53
+ children: [],
54
+ hasChildren: true,
55
+ data: root,
56
+ };
57
+ addChildrenArray(data.children, formatData[root]);
58
+
59
+ let nodeChildren = [...data.children];
60
+ let pathChildren = [...formatData[root].children];
61
+
62
+ while (nodeChildren.length) {
63
+ const size = nodeChildren.length;
64
+ for (let i = 0; i < size; i++) {
65
+ const child = nodeChildren.shift();
66
+ const path = pathChildren.shift();
67
+ nodeChildren = nodeChildren.concat(child.children);
68
+
69
+ switch (child.type) {
70
+ case 'DIRECTORY':
71
+ const dirPath = path;
72
+ formatData[dirPath] = {
73
+ index: dirPath,
74
+ hasChildren: true,
75
+ children: [],
76
+ data: child.name,
77
+ };
78
+ addChildrenArray(child.children, formatData[dirPath]);
79
+ pathChildren = pathChildren.concat(formatData[dirPath].children);
80
+ break;
81
+ case 'FILE':
82
+ const filePath = path;
83
+ formatData[filePath] = {
84
+ index: filePath,
85
+ hasChildren: false,
86
+ data: child.name,
87
+ };
88
+ break;
89
+ default:
90
+ break;
91
+ }
92
+ }
93
+ }
94
+ return formatData;
95
+ }
96
+
97
+ // FileTreeProps
98
+ const FileTree: React.FC<FileTreeProps> = ({
99
+ onCustomSelect,
100
+ onCustomUpload,
101
+ // ...props
102
+ }) => {
103
+ const addedFolder: AddedFiles = { target: '', files: [] };
104
+ const io = useOT((state) => state.socket);
105
+
106
+ const [focusedItem, setFocusedItem] = useState<string>();
107
+ const [expandedItems, setExpandedItems] = useState([]);
108
+ const [selectedItems, setSelectedItems] = useState([]);
109
+ const [remoteUserList, setRemoteUserList] = useState({});
110
+ const treeRef = useRef(null);
111
+ const { CRDTInfo, dockerInfo } = oTStore((state) => state);
112
+
113
+ /**
114
+ * @description initial tree structure area
115
+ * */
116
+ const { fileTree } = oTStore((state) => state);
117
+ const [treeData, setTreeData] = useState<
118
+ Record<TreeItemIndex, TreeItem<string>>
119
+ >({});
120
+ useEffect(() => {
121
+ if (oTStore.getState().appStatus === 'replay') {
122
+ setSelectedItems([]);
123
+ setExpandedItems([]);
124
+ }
125
+ const newData = formatFileToTree(fileTree.data);
126
+ setSelectedItems(selectedItems.filter((item) => newData[item]));
127
+ setExpandedItems(expandedItems.filter((item) => newData[item]));
128
+ setTreeData(newData);
129
+ }, [fileTree]);
130
+
131
+ /* 切换dockerInfo时候清空选项缓存 */
132
+ useEffect(() => {
133
+ setSelectedItems([]);
134
+ setExpandedItems([]);
135
+ }, [dockerInfo]);
136
+
137
+ /**
138
+ * @description fundamental event listener: onSelect, onExpand, onCollapse, onFocus...
139
+ * */
140
+ const onSelect = (items: TreeItemIndex[]) => {
141
+ if (items.length === 1) {
142
+ setSelectedItems(items);
143
+ if (oTStore.getState().appStatus === 'code') {
144
+ const crdt: D42_FrontType.CRDT = {
145
+ timestamp: Date.now().toString(),
146
+ userInfo: userStore.getState().userInfo,
147
+ editor: {
148
+ extraInfo: {
149
+ messageId: '1',
150
+ playgroundId: oTStore.getState().playgroundInfo.playgroundId,
151
+ },
152
+ evtType: 'File',
153
+ },
154
+ file: {
155
+ action: 'Get',
156
+ path: items[0],
157
+ // redisKey: ''
158
+ },
159
+ };
160
+ io.emit('fileContent', JSON.stringify(crdt));
161
+ }
162
+ }
163
+ };
164
+ const onExpand = (item: string) => {
165
+ setExpandedItems([...expandedItems, item]);
166
+ };
167
+ const onCollapse = (item: string) => {
168
+ setExpandedItems(
169
+ expandedItems.filter((expandedItemIndex) => expandedItemIndex !== item),
170
+ );
171
+ };
172
+
173
+ /**
174
+ * @description logic of upload area
175
+ * @description function getStringFromFile => for getting base64 String from file Object
176
+ * @description function traverseFileTree => for flatten web outer transferData to file Array
177
+ * @description function onOriginDrop => for listening web origin drop event, getting web outer transferData, uploading file list
178
+ * @description useEffect => add origin event listener for uploading files
179
+ * @description function onUploadFile => io interface for uploading
180
+ * */
181
+ const getStringFromFile = async (file: File): Promise<string> => {
182
+ const base64String: string = await new Promise((resolve) => {
183
+ const reader = new FileReader();
184
+ reader.readAsDataURL(file);
185
+ reader.onload = (e) => {
186
+ resolve(e.target.result as string);
187
+ };
188
+ });
189
+ return base64String;
190
+ };
191
+
192
+ const traverseFileTree = async (item, path = '/'): Promise<UploadFile[]> => {
193
+ const data: UploadFile[] = await new Promise((resolve) => {
194
+ if (item.isFile) {
195
+ // Get file
196
+ item.file(async (file) => {
197
+ resolve([
198
+ {
199
+ path: `${path}${file.name}`,
200
+ content: await getStringFromFile(file),
201
+ },
202
+ ]);
203
+ });
204
+ } else if (item.isDirectory) {
205
+ // Get folder contents
206
+ let result = [
207
+ {
208
+ path: `${path}${item.name}/`,
209
+ content: '',
210
+ },
211
+ ];
212
+ const dirReader = item.createReader();
213
+ dirReader.readEntries(async (entries) => {
214
+ for (let i = 0; i < entries.length; i++) {
215
+ result = result.concat(
216
+ await traverseFileTree(entries[i], `${path}${item.name}/`),
217
+ );
218
+ }
219
+ resolve(result);
220
+ });
221
+ }
222
+ });
223
+ return data;
224
+ };
225
+
226
+ const onOriginDrop = async (event) => {
227
+ event.preventDefault();
228
+
229
+ let buttonElement = event.target;
230
+ while (buttonElement && buttonElement.tagName?.toLowerCase() !== 'button') {
231
+ buttonElement = buttonElement.lastChild;
232
+ }
233
+ const node = buttonElement ? buttonElement.dataset['rctItemId'] : '/';
234
+ if (node[node.length - 1] !== '/') {
235
+ addedFolder.target = `${node.split('/').slice(0, -1).join('/')}/`;
236
+ } else {
237
+ addedFolder.target = node;
238
+ }
239
+ const items = event.dataTransfer.items;
240
+ if (items.length === 0) return;
241
+ for (let i = 0; i < items.length; i++) {
242
+ // webkitGetAsEntry is where the magic happens
243
+ const item = items[i].webkitGetAsEntry();
244
+ if (item) {
245
+ addedFolder.files = addedFolder.files.concat(
246
+ await traverseFileTree(item),
247
+ );
248
+ }
249
+ }
250
+ buttonElement?.parentNode.classList.remove(
251
+ 'rct-tree-item-title-container-selected',
252
+ );
253
+ buttonElement?.parentNode.parentNode.classList.remove(
254
+ 'rct-tree-item-li-selected',
255
+ );
256
+ if (onCustomUpload) {
257
+ onCustomUpload({ ...addedFolder });
258
+ } else {
259
+ onUploadFile(JSON.stringify(addedFolder));
260
+ }
261
+ addedFolder.target = '';
262
+ addedFolder.files = [];
263
+ };
264
+
265
+ useEffect(() => {
266
+ if (treeRef.current) {
267
+ treeRef.current.removeEventListener('drop', onOriginDrop);
268
+ treeRef.current.addEventListener('drop', onOriginDrop, false);
269
+ treeRef.current.addEventListener(
270
+ 'dragover',
271
+ (e) => {
272
+ if (e.dataTransfer.items.length === 0) return;
273
+ e.preventDefault();
274
+ e.stopPropagation();
275
+ },
276
+ false,
277
+ );
278
+ treeRef.current.addEventListener(
279
+ 'dragenter',
280
+ (e) => {
281
+ if (e.dataTransfer.items.length === 0) return;
282
+ e.preventDefault();
283
+ e.stopPropagation();
284
+ if (e.target.tagName.toLowerCase() === 'button') {
285
+ e.target.parentNode.classList.add(
286
+ 'rct-tree-item-title-container-selected',
287
+ );
288
+ e.target.parentNode.parentNode.classList.add(
289
+ 'rct-tree-item-li-selected',
290
+ );
291
+ }
292
+ },
293
+ false,
294
+ );
295
+ treeRef.current.addEventListener(
296
+ 'dragleave',
297
+ (e) => {
298
+ if (e.dataTransfer.items.length === 0) return;
299
+ e.preventDefault();
300
+ e.stopPropagation();
301
+ if (e.target.tagName.toLowerCase() === 'button') {
302
+ e.target.parentNode.classList.remove(
303
+ 'rct-tree-item-title-container-selected',
304
+ );
305
+ e.target.parentNode.parentNode.classList.remove(
306
+ 'rct-tree-item-li-selected',
307
+ );
308
+ }
309
+ },
310
+ false,
311
+ );
312
+ }
313
+ }, [treeRef]);
314
+
315
+ const onUploadFile = (payload: string) => {
316
+ io.emit('upload', payload);
317
+ };
318
+
319
+ /**
320
+ * @description remote user operation will be synchronized in other or local view
321
+ * */
322
+ useEffect(() => {
323
+ const user = userListStore
324
+ .getState()
325
+ .userList.find((item) => item.uuid === CRDTInfo.userInfo.uuid);
326
+ if (CRDTInfo?.editor?.evtType === 'File' && user) {
327
+ const path = CRDTInfo.file.path as string;
328
+ if (!IsMe(CRDTInfo.userInfo)) {
329
+ setRemoteUserList((oldUserListItem) => {
330
+ const userListItem = { ...oldUserListItem };
331
+ // clear old data
332
+ const remoteUserListObj = Object.entries(userListItem);
333
+ for (const iteratorItem of remoteUserListObj) {
334
+ const [key, value] = iteratorItem as [string, any[]];
335
+ const index = value.findIndex((item) => item.uuid === user.uuid);
336
+ if (index > -1) {
337
+ userListItem[key].splice(index, 1);
338
+ if (userListItem[key].length === 0) {
339
+ delete userListItem[key];
340
+ }
341
+ break;
342
+ }
343
+ }
344
+ //append new data
345
+ if (userListItem[path]) {
346
+ userListItem[path].push({
347
+ uuid: user.uuid,
348
+ color: user.color,
349
+ avatar: user.avatar,
350
+ });
351
+ } else {
352
+ userListItem[path] = [
353
+ {
354
+ uuid: user.uuid,
355
+ color: user.color,
356
+ avatar: user.avatar,
357
+ },
358
+ ];
359
+ }
360
+ return userListItem;
361
+ });
362
+ if (path[path.length - 1] === '/') {
363
+ const index = expandedItems.indexOf(path);
364
+ if (index === -1) {
365
+ onExpand(path);
366
+ } else {
367
+ onCollapse(path);
368
+ }
369
+ }
370
+ } else if (oTStore.getState().appStatus === 'replay') {
371
+ onSelect([path]);
372
+ if (path[path.length - 1] === '/') {
373
+ const index = expandedItems.indexOf(path);
374
+ if (index === -1) {
375
+ onExpand(path);
376
+ } else {
377
+ onCollapse(path);
378
+ }
379
+ }
380
+ }
381
+ }
382
+ }, [CRDTInfo]);
383
+
384
+ return (
385
+ <div ref={treeRef} style={{ height: '100%', overflow: 'auto' }}>
386
+ <ControlledTreeEnvironment
387
+ canDragAndDrop={true}
388
+ items={treeData}
389
+ onFocusItem={(item) => setFocusedItem(item.index as string)}
390
+ onExpandItem={(item) => {
391
+ onExpand(item.index as string);
392
+ }}
393
+ onCollapseItem={(item) => {
394
+ onCollapse(item.index as string);
395
+ }}
396
+ getItemTitle={(item) => item.data}
397
+ viewState={{
398
+ ['tree-1']: {
399
+ focusedItem,
400
+ expandedItems,
401
+ selectedItems,
402
+ },
403
+ }}
404
+ onSelectItems={(items) => {
405
+ onSelect(items);
406
+ onCustomSelect?.(items);
407
+ }}
408
+ renderItem={({ title, arrow, depth, context, children }) => {
409
+ return (
410
+ <div
411
+ {...context.itemContainerWithChildrenProps}
412
+ className={[
413
+ 'rct-tree-item-li',
414
+ context.isSelected ? 'rct-tree-item-li-selected' : '',
415
+ context.isExpanded ? 'rct-tree-item-li-expanded' : '',
416
+ context.isFocused ? 'rct-tree-item-li-focused' : '',
417
+ children ? 'rct-tree-item-li-hasChildren' : '',
418
+ ].join(' ')}
419
+ >
420
+ <div
421
+ className={[
422
+ 'rct-tree-item-title-container',
423
+ context.isSelected
424
+ ? 'rct-tree-item-title-container-selected'
425
+ : '',
426
+ context.isExpanded
427
+ ? 'rct-tree-item-title-container-expanded'
428
+ : '',
429
+ context.isFocused
430
+ ? 'rct-tree-item-title-container-focused'
431
+ : '',
432
+ children ? 'rct-tree-item-title-container-hasChildren' : '',
433
+ ].join(' ')}
434
+ style={{ paddingLeft: `${depth}0px` }}
435
+ >
436
+ {arrow}
437
+ <button
438
+ {...context.itemContainerWithoutChildrenProps}
439
+ {...context.interactiveElementProps}
440
+ className={[
441
+ 'rct-tree-item-button',
442
+ context.isSelected ? 'rct-tree-item-button-selected' : '',
443
+ context.isExpanded ? 'rct-tree-item-button-expanded' : '',
444
+ context.isFocused ? 'rct-tree-item-button-focused' : '',
445
+ children ? 'rct-tree-item-button-hasChildren' : '',
446
+ ].join(' ')}
447
+ >
448
+ {title}
449
+ </button>
450
+ {remoteUserList[
451
+ context.interactiveElementProps['data-rct-item-id']
452
+ ] &&
453
+ remoteUserList[
454
+ context.interactiveElementProps['data-rct-item-id']
455
+ ].map((remoteUser) => (
456
+ <div
457
+ className="rct-tree-item-avatar rounded-full absolute border"
458
+ key={remoteUser.uuid}
459
+ style={{
460
+ borderColor: remoteUser.color,
461
+ backgroundImage: `url(${remoteUser.avatar})`,
462
+ }}
463
+ ></div>
464
+ ))}
465
+ </div>
466
+ {children}
467
+ </div>
468
+ );
469
+ }}
470
+ >
471
+ <Tree treeId="tree-1" rootItem="/" treeLabel="File Tree" />
472
+ </ControlledTreeEnvironment>
473
+ </div>
474
+ );
475
+ };
476
+
477
+ export { FileTree };
@@ -0,0 +1,221 @@
1
+ import styled from '@emotion/styled';
2
+ import React, { useEffect, useState } from 'react';
3
+ import ReactDOM from 'react-dom';
4
+ import { Conn, D42RTC, ID, Events } from '@dao42/d42paas_rtc';
5
+ import VideoComponent from '~/components/LiveContent/video';
6
+ import { IsMe } from '~/helpers';
7
+ import { oTStore, userStore } from '~/stores/oTStore';
8
+ import { getLocalMedia } from '~/helpers/collections/idb';
9
+
10
+ const LiveLayout = styled.div`
11
+ button {
12
+ color: #fff;
13
+ margin: 3px;
14
+ background: #374151;
15
+ padding: 3px;
16
+ border-radius: 3px;
17
+ }
18
+ `;
19
+
20
+ const VideoLayout = styled.div`
21
+ position: fixed;
22
+ right: 20px;
23
+ top: 20px;
24
+ width: 300px;
25
+ min-height: 225px;
26
+ z-index: 10;
27
+ border: 1px solid #d4d4d4;
28
+ border-radius: 4px;
29
+ background-color: #d4d4d4;
30
+ `;
31
+
32
+ const currentUserId = userStore.getState().userInfo.uuid;
33
+
34
+ export interface UserMedia {
35
+ id: string;
36
+ videoStream?: MediaStream | null;
37
+ audioStream?: MediaStream | null;
38
+ segmentId?: string;
39
+ }
40
+
41
+ const LiveContent: React.FC<{
42
+ count?: number;
43
+ }> = ({}) => {
44
+ const [callingUserMedias, setCallingUserMedias] = useState<UserMedia[]>([]);
45
+ const [d42rtc, setD42RTC] = useState<D42RTC>(null);
46
+ const [joining, setJoining] = useState<boolean>(false);
47
+ const [visible, setVisible] = useState<boolean>(false);
48
+ const [replaying, setReplaying] = useState<boolean>(false);
49
+ const [segmentId, setSegmentId] = useState<string[]>(['']);
50
+
51
+ const [currentID] = useState<ID>({
52
+ // TODO: this need change
53
+ uid: currentUserId,
54
+ cid: oTStore.getState().playgroundInfo.playgroundId,
55
+ sid: '0',
56
+ });
57
+
58
+ const initRTC = (): void => {
59
+ setD42RTC((d42rtc) => {
60
+ if (d42rtc) return d42rtc;
61
+ // TODO delete Conn
62
+ const conn = new Conn({
63
+ send: () => {},
64
+ });
65
+
66
+ const rtc = new D42RTC(
67
+ 'https://develop.1024paas.com',
68
+ currentID,
69
+ ['agora'],
70
+ conn,
71
+ );
72
+ if (localStorage.getItem('d42rtc')) {
73
+ rollBackRTCState(rtc);
74
+ }
75
+ rtc.onVideoStream = (id: ID, stream: MediaStream) => {
76
+ setVisible(true);
77
+ maintainUserMediaList({
78
+ id: id.uid,
79
+ videoStream: stream,
80
+ });
81
+ };
82
+ rtc.onAudioStream = (id: ID, stream: MediaStream) => {
83
+ setVisible(true);
84
+ maintainUserMediaList({
85
+ id: id.uid,
86
+ audioStream: stream,
87
+ });
88
+ };
89
+
90
+ rtc.on(Events.userAudioClose, (id: ID) => {
91
+ const audio = document.querySelector(
92
+ `#audio-${id.uid}`,
93
+ ) as HTMLAudioElement;
94
+ audio.srcObject = null;
95
+ audio.pause();
96
+ });
97
+ rtc.on(Events.userVideoClose, (id: ID) => {
98
+ closeVideoWindow(id.uid);
99
+ });
100
+ return rtc;
101
+ });
102
+ };
103
+
104
+ const maintainUserMediaList = (userMedia: UserMedia) => {
105
+ setCallingUserMedias((callingUserMedias) => {
106
+ const existIndex = callingUserMedias.findIndex(
107
+ (item) => item.id === userMedia.id,
108
+ );
109
+ if (existIndex === -1) {
110
+ if (userMedia.id === currentUserId) {
111
+ return [userMedia, ...callingUserMedias];
112
+ } else {
113
+ return [...callingUserMedias, userMedia];
114
+ }
115
+ }
116
+ if (existIndex > -1) {
117
+ const existCallingUserMedia = callingUserMedias[existIndex];
118
+ const newCallingUserMedia = { ...existCallingUserMedia, ...userMedia };
119
+ callingUserMedias.splice(existIndex, 1, newCallingUserMedia);
120
+ return [...callingUserMedias];
121
+ }
122
+ });
123
+ };
124
+
125
+ const rollBackRTCState = (rtc: D42RTC) => {
126
+ rtc.setState(JSON.parse(localStorage.getItem('d42rtc')));
127
+ };
128
+
129
+ const joinOrLeaveHome = (rtc, joining) => {
130
+ setJoining(joining);
131
+ if (joining) {
132
+ rtc.join();
133
+ } else {
134
+ rtc.leave();
135
+ localStorage.removeItem('d42rtc');
136
+ }
137
+ };
138
+
139
+ const startVideo = (rtc: D42RTC) => {
140
+ setReplaying(false);
141
+ setVisible(true);
142
+ rtc.startVideo();
143
+ };
144
+
145
+ const closeVideoWindow = (uuid) => {
146
+ setCallingUserMedias((callingUserMedias) => {
147
+ return callingUserMedias.filter((item) => item.id !== uuid);
148
+ });
149
+ };
150
+
151
+ const replayMedia = async () => {
152
+ const media = await getLocalMedia();
153
+ setReplaying(true);
154
+ setCallingUserMedias([media[0].userInfo.uuid]);
155
+ setSegmentId([media[0].media.extraInfo.segmentId]);
156
+ setVisible(true);
157
+ };
158
+
159
+ const handleLocalAction = () => {};
160
+
161
+ useEffect(() => {
162
+ if (d42rtc) {
163
+ localStorage.setItem('d42rtc', JSON.stringify(d42rtc.getState()));
164
+ }
165
+ if (callingUserMedias.length === 0) {
166
+ setVisible(false);
167
+ }
168
+ }, [callingUserMedias]);
169
+
170
+ useEffect(() => {
171
+ initRTC();
172
+ }, []);
173
+
174
+ return (
175
+ <LiveLayout className="flex items-center">
176
+ {joining ? (
177
+ <>
178
+ <button
179
+ className="button"
180
+ onClick={() => joinOrLeaveHome(d42rtc, false)}
181
+ >
182
+ 离开房间
183
+ </button>
184
+ <button className="button" onClick={() => startVideo(d42rtc)}>
185
+ 发起视频
186
+ </button>
187
+ </>
188
+ ) : (
189
+ <button
190
+ className="button"
191
+ onClick={() => joinOrLeaveHome(d42rtc, true)}
192
+ >
193
+ 加入房间
194
+ </button>
195
+ )}
196
+ <button className="button" onClick={replayMedia}>
197
+ 视频回放
198
+ </button>
199
+ {ReactDOM.createPortal(
200
+ <VideoLayout style={{ display: visible ? 'block' : 'none' }}>
201
+ {callingUserMedias.map((item, index) => (
202
+ <VideoComponent
203
+ key={item.id}
204
+ userMedia={item}
205
+ isRemote={!IsMe({ uuid: item.id })}
206
+ rtc={d42rtc}
207
+ rtcID={currentID}
208
+ currentId={item.id}
209
+ closeVideoWindow={closeVideoWindow}
210
+ replaying={replaying}
211
+ segmentId={segmentId[index]}
212
+ />
213
+ ))}
214
+ </VideoLayout>,
215
+ document.body,
216
+ )}
217
+ </LiveLayout>
218
+ );
219
+ };
220
+
221
+ export default LiveContent;