@streamplace/components 0.6.37 → 0.7.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.
- package/dist/components/chat/chat-box.js +109 -0
- package/dist/components/chat/chat-message.js +76 -0
- package/dist/components/chat/chat.js +56 -0
- package/dist/components/chat/mention-suggestions.js +39 -0
- package/dist/components/chat/mod-view.js +33 -0
- package/dist/components/mobile-player/fullscreen.js +69 -0
- package/dist/components/mobile-player/fullscreen.native.js +151 -0
- package/dist/components/mobile-player/player.js +103 -0
- package/dist/components/mobile-player/props.js +1 -0
- package/dist/components/mobile-player/shared.js +51 -0
- package/dist/components/mobile-player/ui/countdown.js +79 -0
- package/dist/components/mobile-player/ui/index.js +5 -0
- package/dist/components/mobile-player/ui/input.js +38 -0
- package/dist/components/mobile-player/ui/metrics.js +40 -0
- package/dist/components/mobile-player/ui/streamer-context-menu.js +4 -0
- package/dist/components/mobile-player/ui/viewer-context-menu.js +20 -0
- package/dist/components/mobile-player/use-webrtc.js +232 -0
- package/dist/components/mobile-player/video.js +375 -0
- package/dist/components/mobile-player/video.native.js +238 -0
- package/dist/components/mobile-player/webrtc-diagnostics.js +106 -0
- package/dist/components/mobile-player/webrtc-primitives.js +25 -0
- package/dist/components/mobile-player/webrtc-primitives.native.js +1 -0
- package/dist/components/ui/button.js +220 -0
- package/dist/components/ui/dialog.js +203 -0
- package/dist/components/ui/dropdown.js +148 -0
- package/dist/components/ui/icons.js +22 -0
- package/dist/components/ui/index.js +22 -0
- package/dist/components/ui/input.js +202 -0
- package/dist/components/ui/loader.js +7 -0
- package/dist/components/ui/primitives/button.js +121 -0
- package/dist/components/ui/primitives/input.js +202 -0
- package/dist/components/ui/primitives/modal.js +203 -0
- package/dist/components/ui/primitives/text.js +286 -0
- package/dist/components/ui/resizeable.js +101 -0
- package/dist/components/ui/text.js +175 -0
- package/dist/components/ui/textarea.js +17 -0
- package/dist/components/ui/toast.js +129 -0
- package/dist/components/ui/view.js +250 -0
- package/dist/hooks/index.js +9 -0
- package/dist/hooks/useAvatars.js +32 -0
- package/dist/hooks/useCameraToggle.js +9 -0
- package/dist/hooks/useKeyboard.js +33 -0
- package/dist/hooks/useKeyboardSlide.js +11 -0
- package/dist/hooks/useLivestreamInfo.js +62 -0
- package/dist/hooks/useOuterAndInnerDimensions.js +27 -0
- package/dist/hooks/usePlayerDimensions.js +19 -0
- package/dist/hooks/useSegmentTiming.js +62 -0
- package/dist/index.js +10 -0
- package/dist/lib/facet.js +88 -0
- package/dist/lib/theme/atoms.js +620 -0
- package/dist/lib/theme/atoms.types.js +5 -0
- package/dist/lib/theme/index.js +9 -0
- package/dist/lib/theme/theme.js +248 -0
- package/dist/lib/theme/tokens.js +383 -0
- package/dist/lib/utils.js +94 -0
- package/dist/livestream-provider/index.js +8 -3
- package/dist/livestream-store/chat.js +89 -65
- package/dist/livestream-store/index.js +1 -0
- package/dist/livestream-store/livestream-store.js +3 -0
- package/dist/livestream-store/stream-key.js +115 -0
- package/dist/player-store/player-provider.js +0 -1
- package/dist/player-store/player-store.js +13 -0
- package/dist/streamplace-store/block.js +23 -0
- package/dist/streamplace-store/index.js +1 -0
- package/dist/streamplace-store/stream.js +193 -0
- package/node-compile-cache/v22.15.0-x64-efe9a9df-0/37be0eec +0 -0
- package/node-compile-cache/v22.15.0-x64-efe9a9df-0/56540125 +0 -0
- package/node-compile-cache/v22.15.0-x64-efe9a9df-0/67b1eb60 +0 -0
- package/node-compile-cache/v22.15.0-x64-efe9a9df-0/7c275f90 +0 -0
- package/package.json +20 -4
- package/src/components/chat/chat-box.tsx +195 -0
- package/src/components/chat/chat-message.tsx +192 -0
- package/src/components/chat/chat.tsx +128 -0
- package/src/components/chat/mention-suggestions.tsx +71 -0
- package/src/components/chat/mod-view.tsx +118 -0
- package/src/components/mobile-player/fullscreen.native.tsx +193 -0
- package/src/components/mobile-player/fullscreen.tsx +79 -0
- package/src/components/mobile-player/player.tsx +134 -0
- package/src/components/mobile-player/props.tsx +11 -0
- package/src/components/mobile-player/shared.tsx +56 -0
- package/src/components/mobile-player/ui/countdown.tsx +119 -0
- package/src/components/mobile-player/ui/index.ts +5 -0
- package/src/components/mobile-player/ui/input.tsx +85 -0
- package/src/components/mobile-player/ui/metrics.tsx +69 -0
- package/src/components/mobile-player/ui/streamer-context-menu.tsx +3 -0
- package/src/components/mobile-player/ui/viewer-context-menu.tsx +70 -0
- package/src/components/mobile-player/use-webrtc.tsx +282 -0
- package/src/components/mobile-player/video.native.tsx +360 -0
- package/src/components/mobile-player/video.tsx +557 -0
- package/src/components/mobile-player/webrtc-diagnostics.tsx +149 -0
- package/src/components/mobile-player/webrtc-primitives.native.tsx +6 -0
- package/src/components/mobile-player/webrtc-primitives.tsx +33 -0
- package/src/components/ui/button.tsx +309 -0
- package/src/components/ui/dialog.tsx +376 -0
- package/src/components/ui/dropdown.tsx +399 -0
- package/src/components/ui/icons.tsx +50 -0
- package/src/components/ui/index.ts +33 -0
- package/src/components/ui/input.tsx +350 -0
- package/src/components/ui/loader.tsx +9 -0
- package/src/components/ui/primitives/button.tsx +292 -0
- package/src/components/ui/primitives/input.tsx +422 -0
- package/src/components/ui/primitives/modal.tsx +421 -0
- package/src/components/ui/primitives/text.tsx +499 -0
- package/src/components/ui/resizeable.tsx +169 -0
- package/src/components/ui/text.tsx +330 -0
- package/src/components/ui/textarea.tsx +34 -0
- package/src/components/ui/toast.tsx +203 -0
- package/src/components/ui/view.tsx +344 -0
- package/src/hooks/index.ts +9 -0
- package/src/hooks/useAvatars.tsx +44 -0
- package/src/hooks/useCameraToggle.ts +12 -0
- package/src/hooks/useKeyboard.tsx +41 -0
- package/src/hooks/useKeyboardSlide.ts +12 -0
- package/src/hooks/useLivestreamInfo.ts +67 -0
- package/src/hooks/useOuterAndInnerDimensions.tsx +32 -0
- package/src/hooks/usePlayerDimensions.ts +23 -0
- package/src/hooks/useSegmentTiming.tsx +88 -0
- package/src/index.tsx +21 -0
- package/src/lib/facet.ts +131 -0
- package/src/lib/theme/atoms.ts +760 -0
- package/src/lib/theme/atoms.types.ts +258 -0
- package/src/lib/theme/index.ts +48 -0
- package/src/lib/theme/theme.tsx +436 -0
- package/src/lib/theme/tokens.ts +409 -0
- package/src/lib/utils.ts +132 -0
- package/src/livestream-provider/index.tsx +13 -2
- package/src/livestream-store/chat.tsx +115 -78
- package/src/livestream-store/index.tsx +1 -0
- package/src/livestream-store/livestream-state.tsx +3 -0
- package/src/livestream-store/livestream-store.tsx +3 -0
- package/src/livestream-store/stream-key.tsx +124 -0
- package/src/player-store/player-provider.tsx +0 -1
- package/src/player-store/player-state.tsx +28 -0
- package/src/player-store/player-store.tsx +22 -0
- package/src/streamplace-store/block.tsx +29 -0
- package/src/streamplace-store/index.tsx +1 -0
- package/src/streamplace-store/stream.tsx +262 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
import { usePDSAgent } from "../streamplace-store/xrpc";
|
|
3
|
+
export function useAvatars(dids) {
|
|
4
|
+
let agent = usePDSAgent();
|
|
5
|
+
const [profiles, setProfiles] = useState({});
|
|
6
|
+
const inFlight = useRef(new Set());
|
|
7
|
+
const missingDids = useMemo(() => dids.filter((did) => !(did in profiles) && !inFlight.current.has(did)), [dids, profiles]);
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
if (missingDids.length === 0 || !agent)
|
|
10
|
+
return;
|
|
11
|
+
const toFetch = missingDids.slice(0, 25);
|
|
12
|
+
toFetch.forEach((did) => inFlight.current.add(did));
|
|
13
|
+
const fetchProfiles = async () => {
|
|
14
|
+
try {
|
|
15
|
+
const result = await agent.getProfiles({ actors: toFetch });
|
|
16
|
+
const newProfiles = {};
|
|
17
|
+
result.data.profiles.forEach((p) => {
|
|
18
|
+
newProfiles[p.did] = p;
|
|
19
|
+
});
|
|
20
|
+
setProfiles((prev) => ({ ...prev, ...newProfiles }));
|
|
21
|
+
}
|
|
22
|
+
catch (e) {
|
|
23
|
+
console.error("Failed to fetch profiles", e);
|
|
24
|
+
}
|
|
25
|
+
finally {
|
|
26
|
+
toFetch.forEach((did) => inFlight.current.delete(did));
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
fetchProfiles();
|
|
30
|
+
}, [missingDids, agent]);
|
|
31
|
+
return profiles;
|
|
32
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { usePlayerStore } from "../player-store";
|
|
2
|
+
export function useCameraToggle() {
|
|
3
|
+
const ingestCamera = usePlayerStore((x) => x.ingestCamera);
|
|
4
|
+
const setIngestCamera = usePlayerStore((x) => x.setIngestCamera);
|
|
5
|
+
const doSetIngestCamera = () => {
|
|
6
|
+
setIngestCamera(ingestCamera === "user" ? "environment" : "user");
|
|
7
|
+
};
|
|
8
|
+
return { ingestCamera, doSetIngestCamera };
|
|
9
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { Keyboard } from "react-native";
|
|
3
|
+
export function useKeyboard() {
|
|
4
|
+
const [isKeyboardVisible, setIsKeyboardVisible] = useState(false);
|
|
5
|
+
const [keyboardHeight, setKeyboardHeight] = useState(0);
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
const willShowSubscription = Keyboard.addListener("keyboardWillShow", (e) => {
|
|
8
|
+
// setIsKeyboardVisible(true);
|
|
9
|
+
setKeyboardHeight(e.endCoordinates.height);
|
|
10
|
+
console.log("keyboardWillShow", e.endCoordinates.height);
|
|
11
|
+
});
|
|
12
|
+
const willHideSubscription = Keyboard.addListener("keyboardWillHide", (e) => {
|
|
13
|
+
// setIsKeyboardVisible(false);
|
|
14
|
+
setKeyboardHeight(0);
|
|
15
|
+
console.log("keyboardWillHide", e.endCoordinates.height);
|
|
16
|
+
});
|
|
17
|
+
const showSubscription = Keyboard.addListener("keyboardDidShow", (e) => {
|
|
18
|
+
setIsKeyboardVisible(true);
|
|
19
|
+
setKeyboardHeight(e.endCoordinates.height);
|
|
20
|
+
console.log("keyboardDidShow", e.endCoordinates.height);
|
|
21
|
+
});
|
|
22
|
+
const hideSubscription = Keyboard.addListener("keyboardDidHide", () => {
|
|
23
|
+
setIsKeyboardVisible(false);
|
|
24
|
+
setKeyboardHeight(0);
|
|
25
|
+
});
|
|
26
|
+
return () => {
|
|
27
|
+
showSubscription.remove();
|
|
28
|
+
hideSubscription.remove();
|
|
29
|
+
willShowSubscription.remove();
|
|
30
|
+
};
|
|
31
|
+
}, []);
|
|
32
|
+
return { isKeyboardVisible, keyboardHeight };
|
|
33
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { useKeyboard } from "../hooks/useKeyboard";
|
|
2
|
+
import { useOuterAndInnerDimensions } from "../hooks/useOuterAndInnerDimensions";
|
|
3
|
+
export function useKeyboardSlide() {
|
|
4
|
+
const { keyboardHeight } = useKeyboard();
|
|
5
|
+
const { outerHeight, innerHeight } = useOuterAndInnerDimensions();
|
|
6
|
+
let slideKeyboard = 0;
|
|
7
|
+
if (keyboardHeight > 0) {
|
|
8
|
+
slideKeyboard = -keyboardHeight + (outerHeight - innerHeight);
|
|
9
|
+
}
|
|
10
|
+
return { keyboardHeight, slideKeyboard };
|
|
11
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { useLivestreamStore } from "../livestream-store";
|
|
3
|
+
import { usePlayerStore } from "../player-store";
|
|
4
|
+
import { useCreateStreamRecord } from "../streamplace-store";
|
|
5
|
+
export function useLivestreamInfo() {
|
|
6
|
+
const ingest = usePlayerStore((x) => x.ingestConnectionState);
|
|
7
|
+
const profile = useLivestreamStore((x) => x.profile);
|
|
8
|
+
const ingestStarting = usePlayerStore((x) => x.ingestStarting);
|
|
9
|
+
const setIngestStarting = usePlayerStore((x) => x.setIngestStarting);
|
|
10
|
+
const setIngestLive = usePlayerStore((x) => x.setIngestLive);
|
|
11
|
+
const createStreamRecord = useCreateStreamRecord();
|
|
12
|
+
const [title, setTitle] = useState("");
|
|
13
|
+
const [showCountdown, setShowCountdown] = useState(false);
|
|
14
|
+
const [recordSubmitted, setRecordSubmitted] = useState(false);
|
|
15
|
+
const handleSubmit = async () => {
|
|
16
|
+
try {
|
|
17
|
+
if (title !== "") {
|
|
18
|
+
setRecordSubmitted(true);
|
|
19
|
+
await createStreamRecord(title);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
console.error("Error creating livestream:", error);
|
|
24
|
+
throw new Error("Failed to create livestream record");
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
setRecordSubmitted(false);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const toggleGoLive = (keyboardHeight, closeKeyboard) => {
|
|
31
|
+
if (!ingestStarting) {
|
|
32
|
+
// Optionally close keyboard if provided
|
|
33
|
+
if (closeKeyboard)
|
|
34
|
+
closeKeyboard();
|
|
35
|
+
setShowCountdown(true);
|
|
36
|
+
setIngestStarting(true);
|
|
37
|
+
setIngestLive(true);
|
|
38
|
+
// wait ~3 seconds before announcing
|
|
39
|
+
setTimeout(() => {
|
|
40
|
+
handleSubmit();
|
|
41
|
+
}, 3000);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
setIngestStarting(false);
|
|
45
|
+
setIngestLive(false);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
return {
|
|
49
|
+
ingest,
|
|
50
|
+
profile,
|
|
51
|
+
title,
|
|
52
|
+
setTitle,
|
|
53
|
+
showCountdown,
|
|
54
|
+
setShowCountdown,
|
|
55
|
+
recordSubmitted,
|
|
56
|
+
setRecordSubmitted,
|
|
57
|
+
ingestStarting,
|
|
58
|
+
setIngestStarting,
|
|
59
|
+
handleSubmit,
|
|
60
|
+
toggleGoLive,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useCallback, useState } from "react";
|
|
2
|
+
export function useOuterAndInnerDimensions() {
|
|
3
|
+
const [outerDimensions, setOuterDimensions] = useState({
|
|
4
|
+
width: 0,
|
|
5
|
+
height: 0,
|
|
6
|
+
});
|
|
7
|
+
const [innerDimensions, setInnerDimensions] = useState({
|
|
8
|
+
width: 0,
|
|
9
|
+
height: 0,
|
|
10
|
+
});
|
|
11
|
+
const onOuterLayout = useCallback((event) => {
|
|
12
|
+
const { width, height } = event.nativeEvent.layout;
|
|
13
|
+
setOuterDimensions({ width, height });
|
|
14
|
+
}, []);
|
|
15
|
+
const onInnerLayout = useCallback((event) => {
|
|
16
|
+
const { width, height } = event.nativeEvent.layout;
|
|
17
|
+
setInnerDimensions({ width, height });
|
|
18
|
+
}, []);
|
|
19
|
+
return {
|
|
20
|
+
outerWidth: outerDimensions.width,
|
|
21
|
+
outerHeight: outerDimensions.height,
|
|
22
|
+
innerWidth: innerDimensions.width,
|
|
23
|
+
innerHeight: innerDimensions.height,
|
|
24
|
+
onOuterLayout,
|
|
25
|
+
onInnerLayout,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Dimensions } from "react-native";
|
|
2
|
+
import { usePlayerStore } from "../player-store";
|
|
3
|
+
/**
|
|
4
|
+
* usePlayerDimensions
|
|
5
|
+
* Returns player and device dimensions, and whether the player aspect ratio is greater than the device's.
|
|
6
|
+
*/
|
|
7
|
+
export function usePlayerDimensions() {
|
|
8
|
+
const { width, height } = Dimensions.get("window");
|
|
9
|
+
const pHeight = Number(usePlayerStore((x) => x.playerHeight)) || 0;
|
|
10
|
+
const pWidth = Number(usePlayerStore((x) => x.playerWidth)) || 0;
|
|
11
|
+
const isPlayerRatioGreater = pHeight > 0 && pWidth > 0 ? pWidth / pHeight > width / height : false;
|
|
12
|
+
return {
|
|
13
|
+
width,
|
|
14
|
+
height,
|
|
15
|
+
pWidth,
|
|
16
|
+
pHeight,
|
|
17
|
+
isPlayerRatioGreater,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
import { useLivestreamStore } from "../livestream-store";
|
|
3
|
+
function getLiveConnectionQuality(timeBetweenSegments, range, numOfSegments = 1) {
|
|
4
|
+
if (timeBetweenSegments === null || range === null)
|
|
5
|
+
return "poor";
|
|
6
|
+
if (timeBetweenSegments <= 1500 && range <= (1500 * 60) / numOfSegments) {
|
|
7
|
+
return "good";
|
|
8
|
+
}
|
|
9
|
+
if (timeBetweenSegments <= 3000 && range <= (3000 * 60) / numOfSegments) {
|
|
10
|
+
return "degraded";
|
|
11
|
+
}
|
|
12
|
+
return "poor";
|
|
13
|
+
}
|
|
14
|
+
export function useSegmentTiming() {
|
|
15
|
+
const latestSegment = useLivestreamStore((x) => x.segment);
|
|
16
|
+
const [segmentDeltas, setSegmentDeltas] = useState([]);
|
|
17
|
+
const prevSegmentRef = useRef();
|
|
18
|
+
const prevTimestampRef = useRef(null);
|
|
19
|
+
// Dummy state to force update every second
|
|
20
|
+
const [, setNow] = useState(Date.now());
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
const interval = setInterval(() => {
|
|
23
|
+
setNow(Date.now());
|
|
24
|
+
}, 1000);
|
|
25
|
+
return () => clearInterval(interval);
|
|
26
|
+
}, []);
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (latestSegment && prevSegmentRef.current !== latestSegment) {
|
|
29
|
+
const now = Date.now();
|
|
30
|
+
if (prevTimestampRef.current !== null) {
|
|
31
|
+
const delta = now - prevTimestampRef.current;
|
|
32
|
+
// Only store the last 25 deltas
|
|
33
|
+
setSegmentDeltas((prev) => [...prev, delta].slice(-25));
|
|
34
|
+
}
|
|
35
|
+
prevTimestampRef.current = now;
|
|
36
|
+
prevSegmentRef.current = latestSegment;
|
|
37
|
+
}
|
|
38
|
+
}, [latestSegment]);
|
|
39
|
+
// The most recent time between segments
|
|
40
|
+
const timeBetweenSegments = segmentDeltas.length > 0
|
|
41
|
+
? segmentDeltas[segmentDeltas.length - 1]
|
|
42
|
+
: prevTimestampRef.current
|
|
43
|
+
? Date.now() - prevTimestampRef.current
|
|
44
|
+
: null;
|
|
45
|
+
// Calculate mean and range of deltas
|
|
46
|
+
const mean = segmentDeltas.length > 0
|
|
47
|
+
? Math.round(segmentDeltas.reduce((acc, curr) => acc + curr, 0) /
|
|
48
|
+
segmentDeltas.length)
|
|
49
|
+
: null;
|
|
50
|
+
const range = segmentDeltas.length > 0
|
|
51
|
+
? Math.max(...segmentDeltas) - Math.min(...segmentDeltas)
|
|
52
|
+
: null;
|
|
53
|
+
let to_ret = {
|
|
54
|
+
segmentDeltas,
|
|
55
|
+
timeBetweenSegments,
|
|
56
|
+
mean,
|
|
57
|
+
range,
|
|
58
|
+
connectionQuality: "poor",
|
|
59
|
+
};
|
|
60
|
+
to_ret.connectionQuality = getLiveConnectionQuality(timeBetweenSegments, range, segmentDeltas.length);
|
|
61
|
+
return to_ret;
|
|
62
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -4,3 +4,13 @@ export * from "./livestream-store";
|
|
|
4
4
|
export * from "./player-store";
|
|
5
5
|
export * from "./streamplace-provider";
|
|
6
6
|
export * from "./streamplace-store";
|
|
7
|
+
export { PlayerProvider, withPlayerProvider, } from "./player-store/player-provider";
|
|
8
|
+
export { usePlayerContext } from "./player-store/player-store";
|
|
9
|
+
export { Player, PlayerUI } from "./components/mobile-player/player";
|
|
10
|
+
export * as ui from "./components/ui";
|
|
11
|
+
export * from "./components/ui";
|
|
12
|
+
export * as theme from "./lib/theme";
|
|
13
|
+
export * as atoms from "./lib/theme/atoms";
|
|
14
|
+
export * from "./hooks";
|
|
15
|
+
export * from "./components/chat/chat";
|
|
16
|
+
export * from "./components/chat/chat-box";
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
const segment = (text, features) => {
|
|
2
|
+
return { text, features: text.length > 0 ? features : undefined };
|
|
3
|
+
};
|
|
4
|
+
export const segmentize = (text, facets) => {
|
|
5
|
+
if (facets === undefined || facets.length === 0) {
|
|
6
|
+
return [segment(text, undefined)];
|
|
7
|
+
}
|
|
8
|
+
const segments = [];
|
|
9
|
+
const utf16Length = text.length;
|
|
10
|
+
let utf16Cursor = 0;
|
|
11
|
+
let utf8Cursor = 0;
|
|
12
|
+
const advanceCursor = (startUtf16, endUtf8) => {
|
|
13
|
+
let curs = startUtf16;
|
|
14
|
+
// Fast-path for entirely ASCII text
|
|
15
|
+
const isLikelyAsciiText = text.charCodeAt(curs) < 0x80;
|
|
16
|
+
if (isLikelyAsciiText) {
|
|
17
|
+
curs += 1;
|
|
18
|
+
utf8Cursor += 1;
|
|
19
|
+
// SIMD-like batch processing
|
|
20
|
+
while (utf8Cursor + 8 <= endUtf8 && curs + 8 <= utf16Length) {
|
|
21
|
+
const char1 = text.charCodeAt(curs);
|
|
22
|
+
const char2 = text.charCodeAt(curs + 1);
|
|
23
|
+
const char3 = text.charCodeAt(curs + 2);
|
|
24
|
+
const char4 = text.charCodeAt(curs + 3);
|
|
25
|
+
const char5 = text.charCodeAt(curs + 4);
|
|
26
|
+
const char6 = text.charCodeAt(curs + 5);
|
|
27
|
+
const char7 = text.charCodeAt(curs + 6);
|
|
28
|
+
const char8 = text.charCodeAt(curs + 7);
|
|
29
|
+
if ((char1 | char2 | char3 | char4 | char5 | char6 | char7 | char8) <
|
|
30
|
+
0x80) {
|
|
31
|
+
curs += 8;
|
|
32
|
+
utf8Cursor += 8;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// Process remaining characters individually
|
|
39
|
+
while (utf8Cursor < endUtf8 && curs < utf16Length) {
|
|
40
|
+
const code = text.charCodeAt(curs);
|
|
41
|
+
if (code < 0x80) {
|
|
42
|
+
curs += 1;
|
|
43
|
+
utf8Cursor += 1;
|
|
44
|
+
}
|
|
45
|
+
else if (code < 0x800) {
|
|
46
|
+
curs += 1;
|
|
47
|
+
utf8Cursor += 2;
|
|
48
|
+
}
|
|
49
|
+
else if (code < 0xd800 || code > 0xdbff) {
|
|
50
|
+
curs += 1;
|
|
51
|
+
utf8Cursor += 3;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
curs += 2;
|
|
55
|
+
utf8Cursor += 4;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return curs;
|
|
59
|
+
};
|
|
60
|
+
// Process facets
|
|
61
|
+
for (let idx = 0, len = facets.length; idx < len; idx++) {
|
|
62
|
+
const facet = facets[idx];
|
|
63
|
+
const { byteStart, byteEnd } = facet.index;
|
|
64
|
+
const features = facet.features;
|
|
65
|
+
if (byteStart > byteEnd || features.length === 0) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (utf8Cursor < byteStart) {
|
|
69
|
+
const nextUtf16Cursor = advanceCursor(utf16Cursor, byteStart);
|
|
70
|
+
if (nextUtf16Cursor > utf16Cursor) {
|
|
71
|
+
segments.push(segment(text.slice(utf16Cursor, nextUtf16Cursor), undefined));
|
|
72
|
+
}
|
|
73
|
+
utf16Cursor = nextUtf16Cursor;
|
|
74
|
+
}
|
|
75
|
+
{
|
|
76
|
+
const nextUtf16Cursor = advanceCursor(utf16Cursor, byteEnd);
|
|
77
|
+
if (nextUtf16Cursor > utf16Cursor) {
|
|
78
|
+
segments.push(segment(text.slice(utf16Cursor, nextUtf16Cursor), features));
|
|
79
|
+
}
|
|
80
|
+
utf16Cursor = nextUtf16Cursor;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Handle remaining text
|
|
84
|
+
if (utf16Cursor < utf16Length) {
|
|
85
|
+
segments.push(segment(text.slice(utf16Cursor), undefined));
|
|
86
|
+
}
|
|
87
|
+
return segments;
|
|
88
|
+
};
|