react-activity-feed
Version:
React components to create activity and notification feeds
512 lines • 23.7 kB
JavaScript
import { __assign, __awaiter, __generator, __spreadArray } from "tslib";
import { useRef, useState, useCallback, useEffect, useLayoutEffect, } from 'react';
import _uniq from 'lodash/uniq';
import _difference from 'lodash/difference';
import _includes from 'lodash/includes';
import { find as linkifyFind } from 'linkifyjs';
import { useDebouncedCallback } from 'use-debounce';
import { useStreamContext } from '../../context';
import { generateRandomId, dataTransferItemsToFiles, dataTransferItemsHaveFiles, inputValueFromEvent, } from '../../utils';
var defaultOgState = { activeUrl: '', data: {}, order: [] };
var defaultImageState = { data: {}, order: [] };
var defaultFileState = { data: {}, order: [] };
var useTextArea = function () {
var _a = useState(''), text = _a[0], setText = _a[1];
var _b = useState(null), curser = _b[0], setCurser = _b[1];
var textInputRef = useRef();
var insertText = useCallback(function (insertedText) {
setText(function (prevText) {
var textareaElement = textInputRef.current;
if (!textareaElement) {
setCurser(null);
return prevText + insertedText;
}
// Insert emoji at previous cursor position
var selectionStart = textareaElement.selectionStart, selectionEnd = textareaElement.selectionEnd;
setCurser(selectionStart + insertedText.length);
return prevText.slice(0, selectionStart) + insertedText + prevText.slice(selectionEnd);
});
}, []);
var onSelectEmoji = useCallback(function (emoji) { return insertText(emoji.native); }, []);
useLayoutEffect(function () {
// Update cursorPosition after insertText is fired
var textareaElement = textInputRef.current;
if (textareaElement && curser !== null) {
textareaElement.selectionStart = curser;
textareaElement.selectionEnd = curser;
}
}, [curser]);
return { text: text, setText: setText, insertText: insertText, onSelectEmoji: onSelectEmoji, textInputRef: textInputRef };
};
var useOg = function (_a) {
var _b;
var client = _a.client, logErr = _a.logErr;
var _c = useState(defaultOgState), og = _c[0], setOg = _c[1];
var reqInProgress = useRef({});
var activeOg = (_b = og.data[og.activeUrl]) === null || _b === void 0 ? void 0 : _b.data;
var orderedOgStates = og.order.map(function (url) { return og.data[url]; }).filter(Boolean);
var isOgScraping = orderedOgStates.some(function (state) { return state.scrapingActive; });
var availableOg = orderedOgStates.map(function (state) { return state.data; }).filter(Boolean);
var resetOg = useCallback(function () { return setOg(defaultOgState); }, []);
var setActiveOg = useCallback(function (url) {
if (url) {
setOg(function (prevState) {
prevState.data[url].dismissed = false;
return __assign(__assign({}, prevState), { activeUrl: url });
});
}
}, []);
var dismissOg = useCallback(function (e) {
e === null || e === void 0 ? void 0 : e.preventDefault();
setOg(function (prevState) {
for (var url in prevState.data) {
prevState.data[url].dismissed = true;
}
return __assign(__assign({}, prevState), { activeUrl: '' });
});
}, []);
var handleOG = useCallback(function (text) {
var urls = _uniq(linkifyFind(text, 'url').map(function (info) { return info.href; }));
// removed delete ogs from state and add the new urls
setOg(function (prevState) {
var newUrls = _difference(urls, prevState.order);
var removedUrls = _difference(prevState.order, urls);
if (!_includes(urls, prevState.activeUrl)) {
prevState.activeUrl = '';
for (var _i = 0, urls_1 = urls; _i < urls_1.length; _i++) {
var url = urls_1[_i];
var og_1 = prevState.data[url];
if ((og_1 === null || og_1 === void 0 ? void 0 : og_1.data) && !og_1.dismissed) {
prevState.activeUrl = url;
break;
}
}
}
for (var _a = 0, removedUrls_1 = removedUrls; _a < removedUrls_1.length; _a++) {
var url = removedUrls_1[_a];
delete prevState.data[url];
}
for (var _b = 0, newUrls_1 = newUrls; _b < newUrls_1.length; _b++) {
var url = newUrls_1[_b];
prevState.data[url] = { scrapingActive: true, dismissed: false };
}
return __assign(__assign({}, prevState), { order: urls });
});
}, []);
var handleOgDebounced = useDebouncedCallback(handleOG, 750, { leading: true, trailing: true });
useEffect(function () {
og.order
.filter(function (url) { return !reqInProgress.current[url] && og.data[url].scrapingActive; })
.forEach(function (url) { return __awaiter(void 0, void 0, void 0, function () {
var resp_1, e_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
reqInProgress.current[url] = true;
_a.label = 1;
case 1:
_a.trys.push([1, 3, , 4]);
return [4 /*yield*/, client.og(url)];
case 2:
resp_1 = _a.sent();
resp_1.url = url;
setOg(function (prevState) {
prevState.data[url] = __assign(__assign({}, prevState.data[url]), { data: resp_1, scrapingActive: false, dismissed: false });
prevState.activeUrl = prevState.activeUrl || url;
return __assign({}, prevState);
});
return [3 /*break*/, 4];
case 3:
e_1 = _a.sent();
console.warn(e_1);
logErr(e_1, 'get-og');
setOg(function (prevState) {
prevState.data[url] = __assign(__assign({}, prevState.data[url]), { scrapingActive: false, dismissed: false });
return __assign({}, prevState);
});
return [3 /*break*/, 4];
case 4:
delete reqInProgress.current[url];
return [2 /*return*/];
}
});
}); });
}, [og.order]);
return {
og: og,
activeOg: activeOg,
setActiveOg: setActiveOg,
resetOg: resetOg,
availableOg: availableOg,
orderedOgStates: orderedOgStates,
isOgScraping: isOgScraping,
handleOgDebounced: handleOgDebounced,
dismissOg: dismissOg,
ogActiveUrl: og.activeUrl,
};
};
var useUpload = function (_a) {
var client = _a.client, logErr = _a.logErr;
var _b = useState(defaultImageState), images = _b[0], setImages = _b[1];
var _c = useState(defaultFileState), files = _c[0], setFiles = _c[1];
var reqInProgress = useRef({});
var orderedImages = images.order.map(function (id) { return images.data[id]; });
var uploadedImages = orderedImages.filter(function (upload) { return upload.url; });
var orderedFiles = files.order.map(function (id) { return files.data[id]; });
var uploadedFiles = orderedFiles.filter(function (upload) { return upload.url; });
var resetUpload = useCallback(function () {
setImages(defaultImageState);
setFiles(defaultFileState);
}, []);
var uploadNewImage = useCallback(function (file) {
var id = generateRandomId();
setImages(function (_a) {
var order = _a.order, data = _a.data;
data[id] = { id: id, file: file, state: 'uploading' };
return { data: __assign({}, data), order: __spreadArray(__spreadArray([], order, true), [id], false) };
});
if (FileReader) {
// TODO: Possibly use URL.createObjectURL instead. However, then we need
// to release the previews when not used anymore though.
var reader = new FileReader();
reader.onload = function (event) {
var _a;
var previewUri = (_a = event.target) === null || _a === void 0 ? void 0 : _a.result;
if (!previewUri)
return;
setImages(function (prevState) {
if (!prevState.data[id])
return prevState;
prevState.data[id].previewUri = previewUri;
return __assign(__assign({}, prevState), { data: __assign({}, prevState.data) });
});
};
reader.readAsDataURL(file);
}
}, []);
var uploadNewFile = useCallback(function (file) {
var id = generateRandomId();
setFiles(function (_a) {
var order = _a.order, data = _a.data;
data[id] = { id: id, file: file, state: 'uploading' };
return { data: __assign({}, data), order: __spreadArray(__spreadArray([], order, true), [id], false) };
});
}, []);
var uploadImage = useCallback(function (id, img) { return __awaiter(void 0, void 0, void 0, function () {
var url_1, e_2;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
setImages(function (prevState) {
if (!prevState.data[id])
return prevState;
prevState.data[id].state = 'uploading';
return __assign({}, prevState);
});
_a.label = 1;
case 1:
_a.trys.push([1, 3, , 4]);
return [4 /*yield*/, client.images.upload(img.file)];
case 2:
url_1 = (_a.sent()).file;
setImages(function (prevState) {
if (!prevState.data[id])
return prevState;
prevState.data[id].url = url_1;
prevState.data[id].state = 'finished';
return __assign({}, prevState);
});
return [3 /*break*/, 4];
case 3:
e_2 = _a.sent();
console.warn(e_2);
setImages(function (prevState) {
if (!prevState.data[id])
return prevState;
logErr(e_2, 'upload-image');
prevState.data[id].state = 'failed';
return __assign({}, prevState);
});
return [3 /*break*/, 4];
case 4: return [2 /*return*/];
}
});
}); }, []);
var uploadFile = useCallback(function (id, file) { return __awaiter(void 0, void 0, void 0, function () {
var url_2, e_3;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
setFiles(function (prevState) {
if (!prevState.data[id])
return prevState;
prevState.data[id].state = 'uploading';
return __assign(__assign({}, prevState), { data: __assign({}, prevState.data) });
});
_a.label = 1;
case 1:
_a.trys.push([1, 3, , 4]);
return [4 /*yield*/, client.files.upload(file.file)];
case 2:
url_2 = (_a.sent()).file;
setFiles(function (prevState) {
if (!prevState.data[id])
return prevState;
prevState.data[id].url = url_2;
prevState.data[id].state = 'finished';
return __assign(__assign({}, prevState), { data: __assign({}, prevState.data) });
});
return [3 /*break*/, 4];
case 3:
e_3 = _a.sent();
console.warn(e_3);
setFiles(function (prevState) {
if (!prevState.data[id])
return prevState;
logErr(e_3, 'upload-file');
prevState.data[id].state = 'failed';
return __assign(__assign({}, prevState), { data: __assign({}, prevState.data) });
});
return [3 /*break*/, 4];
case 4: return [2 /*return*/];
}
});
}); }, []);
var uploadNewFiles = useCallback(function (files) {
for (var i = 0; i < files.length; i += 1) {
var file = files[i];
if (file.type.startsWith('image/')) {
uploadNewImage(file);
}
else if (file instanceof File) {
uploadNewFile(file);
}
}
}, []);
var removeImage = useCallback(function (id) {
setImages(function (prevState) {
prevState.order = prevState.order.filter(function (oid) { return id !== oid; });
delete prevState.data[id];
return __assign({}, prevState);
});
}, []);
var removeFile = useCallback(function (id) {
// eslint-disable-next-line sonarjs/no-identical-functions
setFiles(function (prevState) {
prevState.order = prevState.order.filter(function (oid) { return id !== oid; });
delete prevState.data[id];
return __assign({}, prevState);
});
}, []);
useEffect(function () {
images.order
.filter(function (id) { return !reqInProgress.current[id] && images.data[id].state === 'uploading'; })
.forEach(function (id) { return __awaiter(void 0, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
reqInProgress.current[id] = true;
return [4 /*yield*/, uploadImage(id, images.data[id])];
case 1:
_a.sent();
delete reqInProgress.current[id];
return [2 /*return*/];
}
});
}); });
}, [images.order]);
useEffect(function () {
files.order
.filter(function (id) { return !reqInProgress.current[id] && files.data[id].state === 'uploading'; })
.forEach(function (id) { return __awaiter(void 0, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
reqInProgress.current[id] = true;
return [4 /*yield*/, uploadFile(id, files.data[id])];
case 1:
_a.sent();
delete reqInProgress.current[id];
return [2 /*return*/];
}
});
}); });
}, [files.order]);
return {
images: images,
files: files,
orderedImages: orderedImages,
orderedFiles: orderedFiles,
uploadedImages: uploadedImages,
uploadedFiles: uploadedFiles,
resetUpload: resetUpload,
uploadNewFiles: uploadNewFiles,
uploadFile: uploadFile,
uploadImage: uploadImage,
removeFile: removeFile,
removeImage: removeImage,
};
};
export function useStatusUpdateForm(_a) {
var _this = this;
var _b;
var activityVerb = _a.activityVerb, feedGroup = _a.feedGroup, modifyActivityData = _a.modifyActivityData, doRequest = _a.doRequest, userId = _a.userId, onSuccess = _a.onSuccess;
var _c = useState(false), submitting = _c[0], setSubmitting = _c[1];
var appCtx = useStreamContext();
var client = appCtx.client;
var userData = (((_b = appCtx.user) === null || _b === void 0 ? void 0 : _b.data) || {});
var logErr = useCallback(function (e, type) { return appCtx.errorHandler(e, type, { userId: userId, feedGroup: feedGroup }); }, []);
var _d = useTextArea(), text = _d.text, setText = _d.setText, insertText = _d.insertText, onSelectEmoji = _d.onSelectEmoji, textInputRef = _d.textInputRef;
var _e = useOg({ client: client, logErr: logErr }), resetOg = _e.resetOg, setActiveOg = _e.setActiveOg, ogActiveUrl = _e.ogActiveUrl, activeOg = _e.activeOg, dismissOg = _e.dismissOg, availableOg = _e.availableOg, isOgScraping = _e.isOgScraping, handleOgDebounced = _e.handleOgDebounced;
var _f = useUpload({ client: client, logErr: logErr }), images = _f.images, files = _f.files, orderedImages = _f.orderedImages, orderedFiles = _f.orderedFiles, uploadedImages = _f.uploadedImages, uploadedFiles = _f.uploadedFiles, resetUpload = _f.resetUpload, uploadNewFiles = _f.uploadNewFiles, uploadFile = _f.uploadFile, uploadImage = _f.uploadImage, removeFile = _f.removeFile, removeImage = _f.removeImage;
var resetState = useCallback(function () {
setText('');
setSubmitting(false);
resetOg();
resetUpload();
}, []);
var object = function () {
for (var _i = 0, orderedImages_1 = orderedImages; _i < orderedImages_1.length; _i++) {
var image = orderedImages_1[_i];
if (image.url)
return image.url;
}
return text.trim();
};
var canSubmit = function () {
return !submitting &&
Boolean(object()) &&
orderedImages.every(function (upload) { return upload.state !== 'uploading'; }) &&
orderedFiles.every(function (upload) { return upload.state !== 'uploading'; }) &&
!isOgScraping;
};
var addActivity = function () { return __awaiter(_this, void 0, void 0, function () {
var activity, modifiedActivity;
var _a;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
activity = {
actor: (_a = client.currentUser) === null || _a === void 0 ? void 0 : _a.ref(),
object: object(),
verb: activityVerb,
text: text.trim(),
attachments: {
og: activeOg,
images: uploadedImages.map(function (image) { return image.url; }).filter(Boolean),
files: uploadedFiles.map(function (upload) { return ({
// url will never actually be empty string because uploadedFiles
// filters those out.
url: upload.url,
name: upload.file.name,
mimeType: upload.file.type,
}); }),
},
};
modifiedActivity = modifyActivityData ? modifyActivityData(activity) : activity;
if (!doRequest) return [3 /*break*/, 2];
return [4 /*yield*/, doRequest(modifiedActivity)];
case 1: return [2 /*return*/, _b.sent()];
case 2: return [4 /*yield*/, client.feed(feedGroup, userId).addActivity(modifiedActivity)];
case 3: return [2 /*return*/, _b.sent()];
}
});
}); };
var onSubmitForm = function (e) { return __awaiter(_this, void 0, void 0, function () {
var response, e_4;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
e.preventDefault();
_a.label = 1;
case 1:
_a.trys.push([1, 3, , 4]);
setSubmitting(true);
return [4 /*yield*/, addActivity()];
case 2:
response = _a.sent();
resetState();
if (onSuccess)
onSuccess(response);
return [3 /*break*/, 4];
case 3:
e_4 = _a.sent();
setSubmitting(false);
logErr(e_4, 'add-activity');
return [3 /*break*/, 4];
case 4: return [2 /*return*/];
}
});
}); };
var onChange = useCallback(function (event) {
var text = inputValueFromEvent(event, true);
if (text === null || text === undefined)
return;
setText(text);
handleOgDebounced(text);
}, []);
var onPaste = useCallback(function (event) { return __awaiter(_this, void 0, void 0, function () {
var items, plainTextPromise, _loop_1, i, state_1, fileLikes, s;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
items = event.clipboardData.items;
if (!dataTransferItemsHaveFiles(items))
return [2 /*return*/];
event.preventDefault();
_loop_1 = function (i) {
var item = items[i];
if (item.kind === 'string' && item.type === 'text/plain') {
plainTextPromise = new Promise(function (resolve) { return item.getAsString(resolve); });
return "break";
}
};
for (i = 0; i < items.length; i += 1) {
state_1 = _loop_1(i);
if (state_1 === "break")
break;
}
return [4 /*yield*/, dataTransferItemsToFiles(items)];
case 1:
fileLikes = _a.sent();
if (fileLikes.length) {
uploadNewFiles(fileLikes);
return [2 /*return*/];
}
if (!plainTextPromise) return [3 /*break*/, 3];
return [4 /*yield*/, plainTextPromise];
case 2:
s = _a.sent();
insertText(s);
_a.label = 3;
case 3: return [2 /*return*/];
}
});
}); }, []);
return {
userData: userData,
textInputRef: textInputRef,
text: text,
submitting: submitting,
files: files,
images: images,
activeOg: activeOg,
availableOg: availableOg,
isOgScraping: isOgScraping,
ogActiveUrl: ogActiveUrl,
onSubmitForm: onSubmitForm,
onSelectEmoji: onSelectEmoji,
insertText: insertText,
onChange: onChange,
dismissOg: dismissOg,
setActiveOg: setActiveOg,
canSubmit: canSubmit,
uploadNewFiles: uploadNewFiles,
uploadFile: uploadFile,
uploadImage: uploadImage,
removeFile: removeFile,
removeImage: removeImage,
onPaste: onPaste,
};
}
//# sourceMappingURL=useStatusUpdateForm.js.map