fastcomments-react-native-sdk
Version:
React Native FastComments Components. Add live commenting to any React Native application.
154 lines (153 loc) • 8.56 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
// use this if you want to use the default layout and layout mechanism
import { CommentAreaMessage } from "./comment-area-message";
import { ActivityIndicator, Alert, BackHandler, View } from "react-native";
import { FastCommentsLiveCommentingService } from "../services/fastcomments-live-commenting";
// @ts-ignore
import { useEffect, useRef, useState } from 'react';
import { useHookstate, useHookstateEffect } from "@hookstate/core";
import { FastCommentsImageAsset } from "../types";
import { LiveCommentingBottomArea } from "./live-commenting-bottom-area";
import { getDefaultFastCommentsStyles } from "../resources";
import { ShowHideCommentsToggle } from "./show-hide-comments-toggle";
import { LiveCommentingList } from "./live-commenting-list";
import { CAN_CLOSE, CAN_NOT_CLOSE, ModalMenu } from "./modal-menu";
import { getCommentMenuItems } from "./comment-menu";
import { makeRequest } from "../services/http";
import { addTranslationsToState } from "../services/translations";
import { mergeSimpleSSO } from "../services/sso";
export function FastCommentsLiveCommenting({ config, styles, callbacks, assets }) {
if (!styles) {
styles = getDefaultFastCommentsStyles();
}
if (config.sso) {
mergeSimpleSSO(config);
}
const serviceInitialState = FastCommentsLiveCommentingService.createFastCommentsStateFromConfig({ ...config }, assets); // shallow clone is important to prevent extra re-renders
const imageAssets = serviceInitialState.imageAssets;
const state = useHookstate(serviceInitialState);
const service = useRef();
useEffect(() => {
service.current = new FastCommentsLiveCommentingService(state, callbacks);
}, []);
const [isLoading, setLoading] = useState(true);
const [isLoaded, setIsLoaded] = useState(false);
const isReplyingToParentIdRef = useRef(null);
const [commentMenuRequest, setCommentMenuRequest] = useState();
const callbackObserver = {};
const callbackObserverRef = useRef(callbackObserver);
const loadAsync = async () => {
if (service.current) {
setLoading(true);
await service.current.fetchRemoteState(false);
setLoading(false);
setIsLoaded(true);
callbacks?.onCommentsRendered && callbacks?.onCommentsRendered(state.commentsTree.get());
}
};
useEffect(() => {
// noinspection JSIgnoredPromiseFromCall
loadAsync();
}, [config.sso?.userDataJSONBase64, config.simpleSSO?.username]); // watching whole config object causes duplicate renders.
useHookstateEffect(() => {
if (isLoaded) {
// noinspection JSIgnoredPromiseFromCall
loadAsync();
}
}, [state.sortDirection]);
useEffect(() => {
const backHandler = BackHandler.addEventListener("hardwareBackPress", () => {
if (isReplyingToParentIdRef.current) {
// noinspection JSIgnoredPromiseFromCall
requestSetReplyingTo(null, true); // TODO request is async and this does not support that yet :(
return true;
}
return false;
});
return () => backHandler.remove();
}, []);
async function requestSetReplyingTo(comment, force) {
// If we're cancelling, and we're already replying to someone, confirm the change.
// It'd be really cool to check if reply area is dirty, if someone wants to add that.
if (!force && !comment && isReplyingToParentIdRef.current) {
if (!state.translations.CONFIRM_CANCEL_REPLY.get()) {
let url = '/translations/widgets/comment-ui-cancel?useFullTranslationIds=true';
if (state.config.locale.get()) {
url += '&locale=' + state.config.locale.get();
}
const translationsResponse = await makeRequest({
apiHost: state.apiHost.get(),
method: 'GET',
url
});
if (translationsResponse.status === 'success') {
addTranslationsToState(state.translations, translationsResponse.translations);
}
}
return new Promise((resolve) => {
Alert.alert(state.translations.CONFIRM_CANCEL_REPLY_TITLE.get(), state.translations.CONFIRM_CANCEL_REPLY.get(), [
{
text: state.translations.CONFIRM_CANCEL_REPLY_CANCEL.get(),
onPress: () => {
resolve(CAN_NOT_CLOSE);
},
style: 'cancel'
},
{
text: state.translations.CONFIRM_CANCEL_REPLY_OK.get(),
onPress: () => {
resolve(CAN_CLOSE);
},
style: 'destructive'
}
], {
onDismiss: () => {
resolve(CAN_NOT_CLOSE);
}
});
});
}
if (comment) {
comment.replyBoxOpen = true;
}
else if (isReplyingToParentIdRef.current) {
state.commentsById[isReplyingToParentIdRef.current].replyBoxOpen.set(false);
}
isReplyingToParentIdRef.current = comment ? comment._id : null;
callbackObserverRef.current.replyingTo && callbackObserverRef.current.replyingTo(comment);
callbacks && callbacks.replyingTo && callbacks.replyingTo(comment);
return CAN_CLOSE;
}
function handleReplySuccess(comment) {
// noinspection JSIgnoredPromiseFromCall
requestSetReplyingTo(null, true);
callbacks && callbacks?.onReplySuccess && callbacks?.onReplySuccess(comment);
}
if (state.blockingErrorMessage.get()) {
return _jsx(View, { style: styles.root, children: _jsx(CommentAreaMessage, { styles: styles, message: state.blockingErrorMessage.get() }) });
}
else if (!((state.commentsTree.length === 0 && state.config.readonly.get()) || ((state.config.hideCommentsUnderCountTextFormat.get() || state.config.useShowCommentsToggle.get()) && !state.commentsVisible.get()))) {
if (isLoading) {
return _jsx(View, { style: [styles.root, styles.loadingOverlay], children: _jsx(ActivityIndicator, { size: "large" }) });
}
console.log('!!!! ************** root re-rendered ************** !!!!');
return _jsxs(View, { style: styles.root, children: [state.commentsVisible.get() && _jsx(LiveCommentingList, { callbacks: callbacks, callbackObserver: callbackObserverRef.current, config: config, onReplySuccess: handleReplySuccess, requestSetReplyingTo: requestSetReplyingTo, imageAssets: imageAssets, openCommentMenu: (comment, menuState) => setCommentMenuRequest({
comment, menuState
}), styles: styles, state: state, service: service }), commentMenuRequest ?
_jsx(ModalMenu, { closeIcon: imageAssets[config.hasDarkBackground ? FastCommentsImageAsset.ICON_CROSS_WHITE : FastCommentsImageAsset.ICON_CROSS], styles: styles, items: getCommentMenuItems({
comment: commentMenuRequest.comment,
onCommentFlagged: callbacks?.onCommentFlagged,
onUserBlocked: callbacks?.onUserBlocked,
pickGIF: callbacks?.pickGIF,
pickImage: callbacks?.pickImage,
styles,
state
}, commentMenuRequest.menuState), isOpen: true, onClose: () => setCommentMenuRequest(undefined) }, commentMenuRequest.comment._id) : null, _jsx(LiveCommentingBottomArea, { callbackObserver: callbackObserverRef.current, imageAssets: imageAssets, onAuthenticationChange: callbacks?.onAuthenticationChange, onNotificationSelected: callbacks?.onNotificationSelected, onReplySuccess: handleReplySuccess, pickGIF: callbacks?.pickGIF, pickImage: callbacks?.pickImage, state: state, styles: styles, translations: state.translations.get() })] });
}
else if (!state.commentsVisible.get() && state.translations.get()) {
return _jsx(View, { style: styles.root, children: _jsx(ShowHideCommentsToggle, { state: state, styles: styles }) });
}
else {
return _jsx(View, { style: styles.root });
}
}