@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.
- package/package.json +3 -2
- package/src/app.tsx +250 -0
- package/src/assets/code-brackets.svg +1 -0
- package/src/assets/colors.svg +1 -0
- package/src/assets/comments.svg +1 -0
- package/src/assets/direction.svg +1 -0
- package/src/assets/flow.svg +1 -0
- package/src/assets/plugin.svg +1 -0
- package/src/assets/repo.svg +1 -0
- package/src/assets/stackalt.svg +1 -0
- package/src/components/Avatar/index.tsx +27 -0
- package/src/components/CanvasHelper/index.tsx +89 -0
- package/src/components/Console/index.tsx +88 -0
- package/src/components/Editor/index.tsx +979 -0
- package/src/components/FileTree/index.tsx +477 -0
- package/src/components/LiveContent/index.tsx +221 -0
- package/src/components/LiveContent/video.tsx +213 -0
- package/src/components/LottieAnim/index.tsx +41 -0
- package/src/components/Message/index.tsx +64 -0
- package/src/components/Model/index.tsx +42 -0
- package/src/components/OutputBrowser/index.tsx +180 -0
- package/src/components/Skeleton/index.tsx +41 -0
- package/src/components/Tabs/index.tsx +23 -0
- package/src/components/Terminal/index.tsx +127 -0
- package/src/components/ToolBar/index.tsx +169 -0
- package/src/components/XTerm/index.tsx +113 -0
- package/src/components/index.tsx +4 -0
- package/src/components/loading/index.tsx +282 -0
- package/src/enum/FExtension.ts +168 -0
- package/src/helpers/collections/IoClient.tsx +314 -0
- package/src/helpers/collections/errorCatcher.tsx +0 -0
- package/src/helpers/collections/idb.tsx +186 -0
- package/src/helpers/collections/localStorage.tsx +13 -0
- package/src/helpers/collections/mock.tsx +30 -0
- package/src/helpers/collections/playgroundInit.tsx +311 -0
- package/src/helpers/collections/replay.tsx +168 -0
- package/src/helpers/collections/socket.tsx +6 -0
- package/src/helpers/collections/toast.tsx +19 -0
- package/src/helpers/collections/userTool.tsx +12 -0
- package/src/helpers/collections/util.tsx +4 -0
- package/src/helpers/index.tsx +6 -0
- package/src/helpers/monaco/monaco-ot-adapter.tsx +476 -0
- package/src/hooks/collections/useOT.tsx +38 -0
- package/src/hooks/index.tsx +1 -0
- package/src/pages/index.tsx +450 -0
- package/src/public/dev.html +35 -0
- package/src/public/index.html +45 -0
- package/src/public/sdkserver.html +35 -0
- package/src/stores/index.tsx +1 -0
- package/src/stores/oTStore.tsx +288 -0
- package/src/stories/BrowserWindow.tsx +30 -0
- package/src/stories/Console.tsx +46 -0
- package/src/stories/Editor.tsx +37 -0
- package/src/stories/FileTree.tsx +50 -0
- package/src/stories/Shell.tsx +53 -0
- package/src/stories/introduction.stories.mdx +193 -0
- package/src/stories/page.tsx +71 -0
- package/src/styles/collections/iconfont.scss +1 -0
- package/src/styles/collections/tabs-costumers.scss +20 -0
- package/src/styles/collections/tailwind.scss +3 -0
- package/src/styles/collections/tree-costumers.scss +53 -0
- package/src/styles/collections/utility.scss +10 -0
- package/src/styles/collections/xterm-costumers.scss +47 -0
- package/src/styles/index.scss +19 -0
- package/src/types/editor.d.ts +31 -0
- 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;
|