agora-appbuilder-core 4.1.13-beta.1 → 4.1.13-beta.3
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 +1 -1
- package/template/customization-api/temp.ts +1 -9
- package/template/defaultConfig.js +2 -2
- package/template/package.json +1 -1
- package/template/src/assets/font-styles.css +4 -0
- package/template/src/assets/fonts/icomoon.ttf +0 -0
- package/template/src/assets/selection.json +1 -1
- package/template/src/atoms/ActionMenu.tsx +1 -1
- package/template/src/atoms/CustomIcon.tsx +1 -0
- package/template/src/atoms/Popup.tsx +1 -1
- package/template/src/components/Controls.tsx +22 -41
- package/template/src/components/whiteboard/StrokeWidthTool.tsx +1 -5
- package/template/src/components/whiteboard/WhiteboardButton.tsx +1 -9
- package/template/src/components/whiteboard/WhiteboardCanvas.tsx +1 -9
- package/template/src/components/whiteboard/WhiteboardConfigure.tsx +215 -95
- package/template/src/components/whiteboard/WhiteboardCursor.tsx +9 -17
- package/template/src/components/whiteboard/WhiteboardToolBox.tsx +1 -15
- package/template/src/components/whiteboard/WhiteboardView.tsx +1 -9
- package/template/src/components/whiteboard/WhiteboardWidget.tsx +1 -4
- package/template/src/components/whiteboard/WhiteboardWrapper.tsx +2 -3
- package/template/src/language/default-labels/videoCallScreenLabels.ts +9 -9
- package/template/src/pages/VideoCall.tsx +2 -1
- package/template/src/pages/video-call/ActionSheetContent.tsx +0 -7
- package/template/src/pages/video-call/SidePanelHeader.tsx +80 -63
- package/template/src/rtm-events/constants.ts +9 -0
- package/template/src/subComponents/caption/Caption.tsx +4 -20
- package/template/src/subComponents/caption/CaptionContainer.tsx +262 -250
- package/template/src/subComponents/caption/CaptionIcon.tsx +6 -4
- package/template/src/subComponents/caption/CaptionText.tsx +26 -20
- package/template/src/subComponents/caption/LanguageSelectorPopup.tsx +30 -142
- package/template/src/subComponents/caption/Transcript.tsx +77 -32
- package/template/src/subComponents/caption/TranscriptIcon.tsx +7 -6
- package/template/src/subComponents/caption/TranslateActionMenu.tsx +128 -0
- package/template/src/subComponents/caption/useCaption.tsx +645 -482
- package/template/src/subComponents/caption/useSTTAPI.tsx +25 -4
- package/template/src/subComponents/caption/useStreamMessageUtils.native.ts +1 -1
- package/template/src/subComponents/caption/useStreamMessageUtils.ts +1 -1
- package/template/src/subComponents/caption/utils.ts +48 -40
- package/template/src/components/whiteboard/FastBoardView.tsx +0 -227
|
@@ -36,6 +36,26 @@ const useSTTAPI = (): IuseSTTAPI => {
|
|
|
36
36
|
const STT_API_URL = `${$config.BACKEND_ENDPOINT}/v1/stt`;
|
|
37
37
|
const localUid = useLocalUid();
|
|
38
38
|
|
|
39
|
+
const roomIdRef = React.useRef(roomId);
|
|
40
|
+
React.useEffect(() => {
|
|
41
|
+
roomIdRef.current = roomId;
|
|
42
|
+
}, [roomId]);
|
|
43
|
+
|
|
44
|
+
const localUidRef = React.useRef(localUid);
|
|
45
|
+
React.useEffect(() => {
|
|
46
|
+
localUidRef.current = localUid;
|
|
47
|
+
}, [localUid]);
|
|
48
|
+
|
|
49
|
+
const tokenRef = React.useRef(store.token);
|
|
50
|
+
React.useEffect(() => {
|
|
51
|
+
tokenRef.current = store.token;
|
|
52
|
+
}, [store.token]);
|
|
53
|
+
|
|
54
|
+
const rtcPropsRef = React.useRef(rtcProps);
|
|
55
|
+
React.useEffect(() => {
|
|
56
|
+
rtcPropsRef.current = rtcProps;
|
|
57
|
+
}, [rtcProps]);
|
|
58
|
+
|
|
39
59
|
const apiCall = async (
|
|
40
60
|
method: 'startv7' | 'update' | 'stopv7',
|
|
41
61
|
botUid: number,
|
|
@@ -49,10 +69,11 @@ const useSTTAPI = (): IuseSTTAPI => {
|
|
|
49
69
|
const ownerUid = botUid - 900000000;
|
|
50
70
|
|
|
51
71
|
let requestBody: any = {
|
|
52
|
-
passphrase:
|
|
72
|
+
passphrase:
|
|
73
|
+
roomIdRef?.current?.host || roomIdRef?.current?.attendee || '',
|
|
53
74
|
dataStream_uid: botUid,
|
|
54
75
|
encryption_mode: $config.ENCRYPTION_ENABLED
|
|
55
|
-
?
|
|
76
|
+
? rtcPropsRef?.current.encryption.mode
|
|
56
77
|
: null,
|
|
57
78
|
};
|
|
58
79
|
|
|
@@ -89,14 +110,14 @@ const useSTTAPI = (): IuseSTTAPI => {
|
|
|
89
110
|
// If method is update and no targets are passed
|
|
90
111
|
requestBody.translate = false;
|
|
91
112
|
}
|
|
92
|
-
requestBody.subscribeAudioUids = [`${
|
|
113
|
+
requestBody.subscribeAudioUids = [`${localUidRef.current}`];
|
|
93
114
|
}
|
|
94
115
|
|
|
95
116
|
const response = await fetch(`${STT_API_URL}/${method}`, {
|
|
96
117
|
method: 'POST',
|
|
97
118
|
headers: {
|
|
98
119
|
'Content-Type': 'application/json',
|
|
99
|
-
authorization:
|
|
120
|
+
authorization: tokenRef?.current ? `Bearer ${tokenRef?.current}` : '',
|
|
100
121
|
'X-Request-Id': requestId,
|
|
101
122
|
'X-Session-Id': logger.getSessionId(),
|
|
102
123
|
},
|
|
@@ -50,7 +50,7 @@ const useStreamMessageUtils = (): {
|
|
|
50
50
|
.lookupType('agora.audio2text.Text')
|
|
51
51
|
.decode(payload as Uint8Array) as any;
|
|
52
52
|
|
|
53
|
-
console.log('[
|
|
53
|
+
console.log('[STT_GLOBAL] stt v7 textstream', botUid, textstream);
|
|
54
54
|
// console.log('STT - Parsed Textstream : ', textstream);
|
|
55
55
|
|
|
56
56
|
// Identifing Current & Prev Speakers for the Captions
|
|
@@ -52,7 +52,7 @@ const useStreamMessageUtils = (): {
|
|
|
52
52
|
.lookupType('agora.audio2text.Text')
|
|
53
53
|
.decode(payload as Uint8Array) as any;
|
|
54
54
|
|
|
55
|
-
console.log('[
|
|
55
|
+
console.log('[STT_GLOBAL] stt v7 textstream', botUid, textstream);
|
|
56
56
|
//console.log('STT - Parsed Textstream : ', textstream);
|
|
57
57
|
// console.log(
|
|
58
58
|
// `STT-callback(${++counter}): %c${textstream.uid} %c${textstream.words
|
|
@@ -110,7 +110,7 @@ export function getLanguageLabel(
|
|
|
110
110
|
languageCode: LanguageType[],
|
|
111
111
|
): string | undefined {
|
|
112
112
|
const langLabels = languageCode.map(langCode => {
|
|
113
|
-
return langData.find(data => data.value === langCode)
|
|
113
|
+
return langData.find(data => data.value === langCode)?.label;
|
|
114
114
|
});
|
|
115
115
|
return langLabels ? langLabels.join(', ') : undefined;
|
|
116
116
|
}
|
|
@@ -163,31 +163,32 @@ export const formatTranscriptContent = (
|
|
|
163
163
|
const formattedContent = meetingTranscript
|
|
164
164
|
.map(item => {
|
|
165
165
|
if (
|
|
166
|
-
item.uid.toString().
|
|
167
|
-
item.uid.toString().
|
|
166
|
+
item.uid.toString().includes('langUpdate') ||
|
|
167
|
+
item.uid.toString().includes('translationUpdate')
|
|
168
168
|
) {
|
|
169
|
-
return `${defaultContent[item?.uid?.split('-')[1]]?.name} ${item.text}`;
|
|
169
|
+
// return `${defaultContent[item?.uid?.split('-')[1]]?.name} ${item.text}`;
|
|
170
|
+
return item.text;
|
|
170
171
|
}
|
|
171
172
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
//
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
.
|
|
186
|
-
|
|
187
|
-
|
|
173
|
+
const speakerName = defaultContent[item.uid]?.name || 'Speaker';
|
|
174
|
+
|
|
175
|
+
// Original
|
|
176
|
+
let entry = `${speakerName}:\n${item.text}`;
|
|
177
|
+
|
|
178
|
+
// Selected Translation
|
|
179
|
+
const storedLang = item.selectedTranslationLanguage;
|
|
180
|
+
const selectedTranslation = storedLang
|
|
181
|
+
? item.translations?.find(t => t.lang === storedLang) || null
|
|
182
|
+
: null;
|
|
183
|
+
if (selectedTranslation) {
|
|
184
|
+
const langLabel =
|
|
185
|
+
langData.find(l => l.value === selectedTranslation.lang)?.label ||
|
|
186
|
+
selectedTranslation.lang;
|
|
187
|
+
|
|
188
|
+
entry += `\n→ (${langLabel}) ${selectedTranslation.text}`;
|
|
188
189
|
}
|
|
189
190
|
|
|
190
|
-
return
|
|
191
|
+
return entry;
|
|
191
192
|
})
|
|
192
193
|
.join('\n\n');
|
|
193
194
|
|
|
@@ -196,13 +197,20 @@ export const formatTranscriptContent = (
|
|
|
196
197
|
);
|
|
197
198
|
|
|
198
199
|
const attendees = Object.entries(defaultContent)
|
|
199
|
-
.filter(
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
200
|
+
.filter(([uid, user]) => {
|
|
201
|
+
const uidNum = Number(uid);
|
|
202
|
+
|
|
203
|
+
const isBot =
|
|
204
|
+
uidNum === 111111 || // STT bot (web)
|
|
205
|
+
uidNum > 900000000 || // STT bots (native)
|
|
206
|
+
uidNum === 100000 || // Recording bot (web user)
|
|
207
|
+
uidNum === 100001; // Recording bot (web screen)
|
|
208
|
+
|
|
209
|
+
const isWaitingRoom = user?.isInWaitingRoom === true;
|
|
210
|
+
|
|
211
|
+
return user.type === 'rtc' && !isBot && !isWaitingRoom;
|
|
212
|
+
})
|
|
213
|
+
.map(([_, user]) => user.name)
|
|
206
214
|
.join(',');
|
|
207
215
|
|
|
208
216
|
const info =
|
|
@@ -232,19 +240,18 @@ export const formatTranscriptContent = (
|
|
|
232
240
|
* @param captionText - The original caption text
|
|
233
241
|
* @param translations - Array of available translations
|
|
234
242
|
* @param viewerSourceLanguage - The user's source (spoken) language
|
|
235
|
-
* @param speakerUid - The UID of the person speaking
|
|
236
|
-
* @param currentUserUid - The UID of the current user
|
|
237
243
|
* @returns The appropriate caption text to display
|
|
238
244
|
*/
|
|
239
245
|
export const getUserTranslatedText = (
|
|
240
246
|
captionText: string,
|
|
241
247
|
translations: Array<{lang: string; text: string; isFinal: boolean}> = [],
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
248
|
+
sourceLanguage: LanguageType,
|
|
249
|
+
selectedTranslationLanguage: LanguageType,
|
|
250
|
+
// speakerUid: string | number,
|
|
251
|
+
// currentUserUid: string | number,
|
|
245
252
|
): {
|
|
246
253
|
value: string;
|
|
247
|
-
|
|
254
|
+
langLabel: string;
|
|
248
255
|
} => {
|
|
249
256
|
// console.log(
|
|
250
257
|
// 'getUserTranslatedText input params',
|
|
@@ -256,16 +263,17 @@ export const getUserTranslatedText = (
|
|
|
256
263
|
// );
|
|
257
264
|
|
|
258
265
|
// 1. If the speaker is the local user, always show their own source text
|
|
259
|
-
if (
|
|
266
|
+
if (!selectedTranslationLanguage) {
|
|
260
267
|
return {
|
|
261
268
|
value: captionText,
|
|
262
|
-
|
|
269
|
+
langLabel: getLanguageLabel([sourceLanguage]) || '',
|
|
263
270
|
};
|
|
264
271
|
}
|
|
272
|
+
|
|
265
273
|
// For other users' captions, try to find translation matching viewer's source language
|
|
266
|
-
if (
|
|
274
|
+
if (selectedTranslationLanguage && translations && translations.length > 0) {
|
|
267
275
|
const matchingTranslation = translations.find(
|
|
268
|
-
t => t.lang ===
|
|
276
|
+
t => t.lang === selectedTranslationLanguage,
|
|
269
277
|
);
|
|
270
278
|
if (matchingTranslation) {
|
|
271
279
|
// Translation exists (even if empty)
|
|
@@ -274,7 +282,7 @@ export const getUserTranslatedText = (
|
|
|
274
282
|
const translatedText = matchingTranslation.text?.trim() || '';
|
|
275
283
|
return {
|
|
276
284
|
value: translatedText,
|
|
277
|
-
|
|
285
|
+
langLabel: getLanguageLabel([selectedTranslationLanguage]) || '',
|
|
278
286
|
};
|
|
279
287
|
}
|
|
280
288
|
}
|
|
@@ -282,7 +290,7 @@ export const getUserTranslatedText = (
|
|
|
282
290
|
// Fallback to original text if no translation found
|
|
283
291
|
return {
|
|
284
292
|
value: captionText,
|
|
285
|
-
|
|
293
|
+
langLabel: 'Original',
|
|
286
294
|
};
|
|
287
295
|
};
|
|
288
296
|
|
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
********************************************
|
|
3
|
-
Copyright © 2021 Agora Lab, Inc., all rights reserved.
|
|
4
|
-
AppBuilder and all associated components, source code, APIs, services, and documentation
|
|
5
|
-
(the "Materials") are owned by Agora Lab, Inc. and its licensors. The Materials may not be
|
|
6
|
-
accessed, used, modified, or distributed for any purpose without a license from Agora Lab, Inc.
|
|
7
|
-
Use without a license or in violation of any license terms and conditions (including use for
|
|
8
|
-
any purpose competitive to Agora Lab, Inc.'s business) is strictly prohibited. For more
|
|
9
|
-
information visit https://appbuilder.agora.io.
|
|
10
|
-
*********************************************
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import React, {useRef, useEffect} from 'react';
|
|
14
|
-
import {StyleSheet, View, Text} from 'react-native';
|
|
15
|
-
import {useRoomInfo} from 'customization-api';
|
|
16
|
-
import {useString} from '../../utils/useString';
|
|
17
|
-
import {whiteboardInitializingText} from '../../language/default-labels/videoCallScreenLabels';
|
|
18
|
-
import {useFastboard, Fastboard, createFastboard, FastboardApp} from '@netless/fastboard-react/full';
|
|
19
|
-
|
|
20
|
-
import {useLocalUid} from '../../../agora-rn-uikit';
|
|
21
|
-
import useUserName from '../../utils/useUserName';
|
|
22
|
-
|
|
23
|
-
interface FastBoardViewProps {
|
|
24
|
-
showToolbox?: boolean;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Technical Note: Why we are not using the 'useFastboard' hook?
|
|
29
|
-
*
|
|
30
|
-
* We replaced the standard 'useFastboard' hook with a manual 'FastboardManager' implementation
|
|
31
|
-
* to solve a critical race condition in React 18+ Strict Mode.
|
|
32
|
-
*
|
|
33
|
-
* Issue: "[WindowManager]: Already created cannot be created again"
|
|
34
|
-
*
|
|
35
|
-
* In Strict Mode, React intentionally mounts, unmounts, and remounts components rapidly
|
|
36
|
-
* to detect side-effects. The standard hook attempts to initialize the Fastboard singleton
|
|
37
|
-
* twice in parallel. The first initialization is still "pending" when the second one starts,
|
|
38
|
-
* causing the underlying WindowManager to crash because it doesn't support concurrent init calls.
|
|
39
|
-
*
|
|
40
|
-
* Solution: The 'FastboardManager' below uses Reference Counting and Singleton pattern to:
|
|
41
|
-
* 1. Deduplicate initialization requests (reusing the same Promise).
|
|
42
|
-
* 2. Delay cleanup slightly to handle the rapid Unmount -> Mount cycle without destroying the instance.
|
|
43
|
-
*/
|
|
44
|
-
const FastBoardView: React.FC<FastBoardViewProps> = ({showToolbox = true}) => {
|
|
45
|
-
const {
|
|
46
|
-
data: {whiteboard: {room_token, room_uuid} = {}, isHost},
|
|
47
|
-
} = useRoomInfo();
|
|
48
|
-
|
|
49
|
-
const localUid = useLocalUid();
|
|
50
|
-
const [name] = useUserName();
|
|
51
|
-
|
|
52
|
-
const whiteboardInitializing = useString(whiteboardInitializingText)();
|
|
53
|
-
|
|
54
|
-
// Generate stable UID - only once
|
|
55
|
-
// Use local user's name or fallback to UID if name is not available
|
|
56
|
-
const uidRef = useRef<string>(name || String(localUid));
|
|
57
|
-
const [fastboard, setFastboard] = React.useState<FastboardApp | null>(null);
|
|
58
|
-
|
|
59
|
-
// Configuration object - Memoize stringified version to detect changes
|
|
60
|
-
const config = React.useMemo(() => ({
|
|
61
|
-
sdkConfig: {
|
|
62
|
-
appIdentifier: 'EEJBQPVbEe2Bao8ZShuoHQ/hgB5eo0qcDbVig',
|
|
63
|
-
region: 'us-sv',
|
|
64
|
-
},
|
|
65
|
-
joinRoom: {
|
|
66
|
-
uid: uidRef.current,
|
|
67
|
-
uuid: room_uuid,
|
|
68
|
-
roomToken: room_token,
|
|
69
|
-
},
|
|
70
|
-
}), [room_uuid, room_token]);
|
|
71
|
-
|
|
72
|
-
const configStr = JSON.stringify(config);
|
|
73
|
-
|
|
74
|
-
useEffect(() => {
|
|
75
|
-
let isMounted = true;
|
|
76
|
-
|
|
77
|
-
const initFastboard = async () => {
|
|
78
|
-
try {
|
|
79
|
-
const app = await FastboardManager.acquire(configStr, config);
|
|
80
|
-
if (isMounted) {
|
|
81
|
-
setFastboard(app);
|
|
82
|
-
}
|
|
83
|
-
} catch (error) {
|
|
84
|
-
console.error("Fastboard initialization failed:", error);
|
|
85
|
-
}
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
initFastboard();
|
|
89
|
-
|
|
90
|
-
return () => {
|
|
91
|
-
isMounted = false;
|
|
92
|
-
FastboardManager.release(configStr);
|
|
93
|
-
};
|
|
94
|
-
}, [configStr, config]);
|
|
95
|
-
|
|
96
|
-
// Show loading if fastboard not ready yet
|
|
97
|
-
if (!fastboard) {
|
|
98
|
-
return (
|
|
99
|
-
<View style={styles.container}>
|
|
100
|
-
<View style={styles.placeholder}>
|
|
101
|
-
<Text style={styles.placeholderText}>{whiteboardInitializing}</Text>
|
|
102
|
-
</View>
|
|
103
|
-
</View>
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return (
|
|
108
|
-
<View style={styles.container}>
|
|
109
|
-
<div style={divStyles.fastboardContainer}>
|
|
110
|
-
<Fastboard app={fastboard} />
|
|
111
|
-
</div>
|
|
112
|
-
</View>
|
|
113
|
-
);
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
// Global Manager to handle Strict Mode and Singleton constraints
|
|
117
|
-
const FastboardManager = {
|
|
118
|
-
activeConfig: null as string | null,
|
|
119
|
-
instance: null as FastboardApp | null,
|
|
120
|
-
promise: null as Promise<FastboardApp> | null,
|
|
121
|
-
subscribers: 0,
|
|
122
|
-
|
|
123
|
-
acquire: async (configStr: string, config: any): Promise<FastboardApp> => {
|
|
124
|
-
FastboardManager.subscribers++;
|
|
125
|
-
|
|
126
|
-
// If config matches and we have an instance/promise, reuse it
|
|
127
|
-
if (FastboardManager.activeConfig === configStr) {
|
|
128
|
-
if (FastboardManager.instance) return FastboardManager.instance;
|
|
129
|
-
if (FastboardManager.promise) return FastboardManager.promise;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Config mismatch! We must cleanup the previous board before creating a new one.
|
|
133
|
-
// This happens if component remounts with new UID (e.g. Pin to Top).
|
|
134
|
-
|
|
135
|
-
// 1. If there's a pending loading operation, wait for it and destroy the result.
|
|
136
|
-
if (FastboardManager.promise) {
|
|
137
|
-
try {
|
|
138
|
-
const oldApp = await FastboardManager.promise;
|
|
139
|
-
// Double check if it wasn't already destroyed or reassigned
|
|
140
|
-
if (oldApp) oldApp.destroy();
|
|
141
|
-
} catch (e) {
|
|
142
|
-
console.warn("Error cleaning up previous pending Fastboard:", e);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// 2. If there's an active instance, destroy it.
|
|
147
|
-
if (FastboardManager.instance) {
|
|
148
|
-
try {
|
|
149
|
-
FastboardManager.instance.destroy();
|
|
150
|
-
} catch (e) {
|
|
151
|
-
console.warn("Error cleaning up previous active Fastboard:", e);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Reset state
|
|
156
|
-
FastboardManager.instance = null;
|
|
157
|
-
FastboardManager.promise = null;
|
|
158
|
-
FastboardManager.activeConfig = configStr;
|
|
159
|
-
|
|
160
|
-
// 3. Create new instance
|
|
161
|
-
FastboardManager.promise = createFastboard(config).then(app => {
|
|
162
|
-
// Check if we are still the active config
|
|
163
|
-
if (FastboardManager.activeConfig === configStr) {
|
|
164
|
-
FastboardManager.instance = app;
|
|
165
|
-
return app;
|
|
166
|
-
} else {
|
|
167
|
-
// Config changed while loading
|
|
168
|
-
app.destroy();
|
|
169
|
-
throw new Error("Fastboard config changed during initialization");
|
|
170
|
-
}
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
return FastboardManager.promise;
|
|
174
|
-
},
|
|
175
|
-
|
|
176
|
-
release: (configStr: string) => {
|
|
177
|
-
FastboardManager.subscribers--;
|
|
178
|
-
|
|
179
|
-
// Delayed cleanup to handle Strict Mode "Unmount -> Mount" flicker
|
|
180
|
-
setTimeout(() => {
|
|
181
|
-
if (FastboardManager.subscribers === 0 && FastboardManager.activeConfig === configStr) {
|
|
182
|
-
if (FastboardManager.instance) {
|
|
183
|
-
FastboardManager.instance.destroy();
|
|
184
|
-
FastboardManager.instance = null;
|
|
185
|
-
}
|
|
186
|
-
FastboardManager.promise = null;
|
|
187
|
-
FastboardManager.activeConfig = null;
|
|
188
|
-
}
|
|
189
|
-
}, 200);
|
|
190
|
-
}
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
const styles = StyleSheet.create({
|
|
194
|
-
container: {
|
|
195
|
-
flex: 1,
|
|
196
|
-
position: 'relative',
|
|
197
|
-
backgroundColor: '#ffffff',
|
|
198
|
-
},
|
|
199
|
-
placeholder: {
|
|
200
|
-
position: 'absolute',
|
|
201
|
-
width: '100%',
|
|
202
|
-
height: '100%',
|
|
203
|
-
backgroundColor: '#f5f5f5',
|
|
204
|
-
display: 'flex',
|
|
205
|
-
justifyContent: 'center',
|
|
206
|
-
alignItems: 'center',
|
|
207
|
-
},
|
|
208
|
-
placeholderText: {
|
|
209
|
-
color: '#666666',
|
|
210
|
-
fontSize: 16,
|
|
211
|
-
},
|
|
212
|
-
errorText: {
|
|
213
|
-
color: '#ff4444',
|
|
214
|
-
fontSize: 16,
|
|
215
|
-
},
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
const divStyles = {
|
|
219
|
-
fastboardContainer: {
|
|
220
|
-
width: '100%',
|
|
221
|
-
height: '100%',
|
|
222
|
-
borderRadius: 4,
|
|
223
|
-
overflow: 'hidden' as const,
|
|
224
|
-
},
|
|
225
|
-
};
|
|
226
|
-
|
|
227
|
-
export default FastBoardView;
|