fix build

This commit is contained in:
Abhinav 2022-07-30 13:50:41 +05:30
parent 3e339eee84
commit 8c6991df90
15 changed files with 2126 additions and 2045 deletions

View file

@ -58,9 +58,9 @@
"libsodium-wrappers": "^0.7.8",
"localforage": "^1.9.0",
"ml-matrix": "^6.8.2",
"p-queue": "^7.1.0",
"next": "^12.1.0",
"next-transpile-modules": "^9.0.0",
"p-queue": "^7.1.0",
"photoswipe": "file:./thirdparty/photoswipe",
"piexifjs": "^1.0.6",
"react": "^17.0.2",
@ -77,10 +77,10 @@
"react-window": "^1.8.6",
"scrypt-js": "^3.0.1",
"similarity-transformation": "^0.0.1",
"styled-components": "^5.3.5",
"tesseract.js": "file:./thirdparty/tesseract",
"transformation-matrix": "^2.10.0",
"tsne-js": "^1.0.3",
"styled-components": "^5.3.5",
"workbox-precaching": "^6.1.5",
"workbox-recipes": "^6.1.5",
"workbox-routing": "^6.1.5",
@ -101,8 +101,8 @@
"@types/react-select": "^4.0.15",
"@types/react-window": "^1.8.2",
"@types/react-window-infinite-loader": "^1.0.3",
"@types/wicg-file-system-access": "^2020.9.5",
"@types/styled-components": "^5.1.25",
"@types/wicg-file-system-access": "^2020.9.5",
"@types/yup": "^0.29.7",
"@typescript-eslint/eslint-plugin": "^4.25.0",
"@typescript-eslint/parser": "^4.25.0",

View file

@ -1,7 +1,7 @@
import React, { useState, useEffect, useContext, ChangeEvent } from 'react';
import { getData, LS_KEYS } from 'utils/storage/localStorage';
import { useRouter } from 'next/router';
import { ComlinkWorker } from 'utils/crypto';
import { ComlinkWorker } from 'utils/comlink';
import { AppContext } from 'pages/_app';
import { PAGES } from 'constants/pages';
import * as Comlink from 'comlink';

View file

@ -0,0 +1,832 @@
export {};
// import React, { useContext, useEffect, useRef, useState } from 'react';
// import Photoswipe from 'photoswipe';
// import PhotoswipeUIDefault from 'photoswipe/dist/photoswipe-ui-default';
// import classnames from 'classnames';
// import FavButton from 'components/FavButton';
// import {
// addToFavorites,
// removeFromFavorites,
// } from 'services/collectionService';
// import { updatePublicMagicMetadata } from 'services/fileService';
// import { EnteFile } from 'types/file';
// import constants from 'utils/strings/constants';
// import exifr from 'exifr';
// import Modal from 'react-bootstrap/Modal';
// import Button from 'react-bootstrap/Button';
// import styled from 'styled-components';
// import events from './events';
// import {
// changeFileCreationTime,
// changeFileName,
// downloadFile,
// formatDateTime,
// splitFilenameAndExtension,
// updateExistingFilePubMetadata,
// } from 'utils/file';
// import { Col, Form, FormCheck, FormControl } from 'react-bootstrap';
// import { prettyPrintExif } from 'utils/exif';
// import EditIcon from 'components/icons/EditIcon';
// import {
// FlexWrapper,
// FreeFlowText,
// IconButton,
// Label,
// Row,
// Value,
// } from 'components/Container';
// import { logError } from 'utils/sentry';
// import CloseIcon from 'components/icons/CloseIcon';
// import TickIcon from 'components/icons/TickIcon';
// import {
// PhotoPeopleList,
// UnidentifiedFaces,
// } from 'components/MachineLearning/PeopleList';
// import { Formik } from 'formik';
// import * as Yup from 'yup';
// import EnteSpinner from 'components/EnteSpinner';
// import EnteDateTimePicker from 'components/EnteDateTimePicker';
// // import { AppContext } from 'pages/_app';
// import { MAX_EDITED_FILE_NAME_LENGTH } from 'constants/file';
// import { sleep } from 'utils/common';
// import { PublicCollectionGalleryContext } from 'utils/publicCollectionGallery';
// import { GalleryContext } from 'pages/gallery';
// import { ObjectLabelList } from 'components/MachineLearning/ObjectList';
// import { WordList } from 'components/MachineLearning/WordList';
// import MLServiceFileInfoButton from 'components/MachineLearning/MLServiceFileInfoButton';
// const SmallLoadingSpinner = () => (
// <EnteSpinner
// style={{
// width: '20px',
// height: '20px',
// }}
// />
// );
// interface Iprops {
// isOpen: boolean;
// items: EnteFile[];
// currentIndex?: number;
// onClose?: (needUpdate: boolean) => void;
// gettingData: (instance: any, index: number, item: EnteFile) => void;
// id?: string;
// className?: string;
// favItemIds: Set<number>;
// isSharedCollection: boolean;
// isTrashCollection: boolean;
// }
// const LegendContainer = styled.div`
// display: flex;
// justify-content: space-between;
// `;
// const Legend = styled.span`
// font-size: 20px;
// color: #ddd;
// display: inline;
// `;
// const Pre = styled.pre`
// color: #aaa;
// padding: 7px 15px;
// `;
// const renderInfoItem = (label: string, value: string | JSX.Element) => (
// <Row>
// <Label width="30%">{label}</Label>
// <Value width="70%">{value}</Value>
// </Row>
// );
// function RenderCreationTime({
// shouldDisableEdits,
// file,
// scheduleUpdate,
// }: {
// shouldDisableEdits: boolean;
// file: EnteFile;
// scheduleUpdate: () => void;
// }) {
// const [loading, setLoading] = useState(false);
// const originalCreationTime = new Date(file?.metadata.creationTime / 1000);
// const [isInEditMode, setIsInEditMode] = useState(false);
// const [pickedTime, setPickedTime] = useState(originalCreationTime);
// const openEditMode = () => setIsInEditMode(true);
// const closeEditMode = () => setIsInEditMode(false);
// const saveEdits = async () => {
// try {
// setLoading(true);
// if (isInEditMode && file) {
// const unixTimeInMicroSec = pickedTime.getTime() * 1000;
// if (unixTimeInMicroSec === file?.metadata.creationTime) {
// closeEditMode();
// return;
// }
// let updatedFile = await changeFileCreationTime(
// file,
// unixTimeInMicroSec
// );
// updatedFile = (
// await updatePublicMagicMetadata([updatedFile])
// )[0];
// updateExistingFilePubMetadata(file, updatedFile);
// scheduleUpdate();
// }
// } catch (e) {
// logError(e, 'failed to update creationTime');
// } finally {
// closeEditMode();
// setLoading(false);
// }
// };
// const discardEdits = () => {
// setPickedTime(originalCreationTime);
// closeEditMode();
// };
// const handleChange = (newDate: Date) => {
// if (newDate instanceof Date) {
// setPickedTime(newDate);
// }
// };
// return (
// <>
// <Row>
// <Label width="30%">{constants.CREATION_TIME}</Label>
// <Value width={isInEditMode ? '50%' : '60%'}>
// {isInEditMode ? (
// <EnteDateTimePicker
// loading={loading}
// isInEditMode={isInEditMode}
// pickedTime={pickedTime}
// handleChange={handleChange}
// />
// ) : (
// formatDateTime(pickedTime)
// )}
// </Value>
// <Value
// width={isInEditMode ? '20%' : '10%'}
// style={{ cursor: 'pointer', marginLeft: '10px' }}>
// {!shouldDisableEdits &&
// (!isInEditMode ? (
// <IconButton onClick={openEditMode}>
// <EditIcon />
// </IconButton>
// ) : (
// <>
// <IconButton onClick={saveEdits}>
// {loading ? (
// <SmallLoadingSpinner />
// ) : (
// <TickIcon />
// )}
// </IconButton>
// <IconButton onClick={discardEdits}>
// <CloseIcon />
// </IconButton>
// </>
// ))}
// </Value>
// </Row>
// </>
// );
// }
// const getFileTitle = (filename, extension) => {
// if (extension) {
// return filename + '.' + extension;
// } else {
// return filename;
// }
// };
// interface formValues {
// filename: string;
// }
// const FileNameEditForm = ({ filename, saveEdits, discardEdits, extension }) => {
// const [loading, setLoading] = useState(false);
// const onSubmit = async (values: formValues) => {
// try {
// setLoading(true);
// await saveEdits(values.filename);
// } finally {
// setLoading(false);
// }
// };
// return (
// <Formik<formValues>
// initialValues={{ filename }}
// validationSchema={Yup.object().shape({
// filename: Yup.string()
// .required(constants.REQUIRED)
// .max(
// MAX_EDITED_FILE_NAME_LENGTH,
// constants.FILE_NAME_CHARACTER_LIMIT
// ),
// })}
// validateOnBlur={false}
// onSubmit={onSubmit}>
// {({ values, errors, handleChange, handleSubmit }) => (
// <Form noValidate onSubmit={handleSubmit}>
// <Form.Row>
// <Form.Group
// bsPrefix="ente-form-group"
// as={Col}
// xs={extension ? 7 : 8}>
// <Form.Control
// as="textarea"
// placeholder={constants.FILE_NAME}
// value={values.filename}
// onChange={handleChange('filename')}
// isInvalid={Boolean(errors.filename)}
// autoFocus
// disabled={loading}
// />
// <FormControl.Feedback
// type="invalid"
// style={{ textAlign: 'center' }}>
// {errors.filename}
// </FormControl.Feedback>
// </Form.Group>
// {extension && (
// <Form.Group
// bsPrefix="ente-form-group"
// as={Col}
// xs={1}
// controlId="formHorizontalFileName">
// <FlexWrapper style={{ padding: '5px' }}>
// {`.${extension}`}
// </FlexWrapper>
// </Form.Group>
// )}
// <Form.Group bsPrefix="ente-form-group" as={Col} xs={2}>
// <Value width={'16.67%'}>
// <IconButton type="submit" disabled={loading}>
// {loading ? (
// <SmallLoadingSpinner />
// ) : (
// <TickIcon />
// )}
// </IconButton>
// <IconButton
// onClick={discardEdits}
// disabled={loading}>
// <CloseIcon />
// </IconButton>
// </Value>
// </Form.Group>
// </Form.Row>
// </Form>
// )}
// </Formik>
// );
// };
// function RenderFileName({
// shouldDisableEdits,
// file,
// scheduleUpdate,
// }: {
// shouldDisableEdits: boolean;
// file: EnteFile;
// scheduleUpdate: () => void;
// }) {
// const originalTitle = file?.metadata.title;
// const [isInEditMode, setIsInEditMode] = useState(false);
// const [originalFileName, extension] =
// splitFilenameAndExtension(originalTitle);
// const [filename, setFilename] = useState(originalFileName);
// const openEditMode = () => setIsInEditMode(true);
// const closeEditMode = () => setIsInEditMode(false);
// const saveEdits = async (newFilename: string) => {
// try {
// if (file) {
// if (filename === newFilename) {
// closeEditMode();
// return;
// }
// setFilename(newFilename);
// const newTitle = getFileTitle(newFilename, extension);
// let updatedFile = await changeFileName(file, newTitle);
// updatedFile = (
// await updatePublicMagicMetadata([updatedFile])
// )[0];
// updateExistingFilePubMetadata(file, updatedFile);
// scheduleUpdate();
// }
// } catch (e) {
// logError(e, 'failed to update file name');
// } finally {
// closeEditMode();
// }
// };
// return (
// <>
// <Row>
// <Label width="30%">{constants.FILE_NAME}</Label>
// {!isInEditMode ? (
// <>
// <Value width="60%">
// <FreeFlowText>
// {getFileTitle(filename, extension)}
// </FreeFlowText>
// </Value>
// {!shouldDisableEdits && (
// <Value
// width="10%"
// style={{
// cursor: 'pointer',
// marginLeft: '10px',
// }}>
// <IconButton onClick={openEditMode}>
// <EditIcon />
// </IconButton>
// </Value>
// )}
// </>
// ) : (
// <FileNameEditForm
// extension={extension}
// filename={filename}
// saveEdits={saveEdits}
// discardEdits={closeEditMode}
// />
// )}
// </Row>
// </>
// );
// }
// function ExifData(props: { exif: any }) {
// const { exif } = props;
// const [showAll, setShowAll] = useState(false);
// const changeHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
// setShowAll(e.target.checked);
// };
// const renderAllValues = () => <Pre>{exif.raw}</Pre>;
// const renderSelectedValues = () => (
// <>
// {exif?.Make &&
// exif?.Model &&
// renderInfoItem(constants.DEVICE, `${exif.Make} ${exif.Model}`)}
// {exif?.ImageWidth &&
// exif?.ImageHeight &&
// renderInfoItem(
// constants.IMAGE_SIZE,
// `${exif.ImageWidth} x ${exif.ImageHeight}`
// )}
// {exif?.Flash && renderInfoItem(constants.FLASH, exif.Flash)}
// {exif?.FocalLength &&
// renderInfoItem(
// constants.FOCAL_LENGTH,
// exif.FocalLength.toString()
// )}
// {exif?.ApertureValue &&
// renderInfoItem(
// constants.APERTURE,
// exif.ApertureValue.toString()
// )}
// {exif?.ISOSpeedRatings &&
// renderInfoItem(constants.ISO, exif.ISOSpeedRatings.toString())}
// </>
// );
// return (
// <>
// <LegendContainer>
// <Legend>{constants.EXIF}</Legend>
// <FormCheck>
// <FormCheck.Label>
// <FormCheck.Input onChange={changeHandler} />
// {constants.SHOW_ALL}
// </FormCheck.Label>
// </FormCheck>
// </LegendContainer>
// {showAll ? renderAllValues() : renderSelectedValues()}
// </>
// );
// }
// function InfoModal({
// shouldDisableEdits,
// showInfo,
// handleCloseInfo,
// items,
// photoSwipe,
// metadata,
// exif,
// scheduleUpdate,
// }) {
// // const appContext = useContext(AppContext);
// const [updateMLDataIndex, setUpdateMLDataIndex] = useState(0);
// return (
// <Modal show={showInfo} onHide={handleCloseInfo}>
// <Modal.Header closeButton>
// <Modal.Title>{constants.INFO}</Modal.Title>
// </Modal.Header>
// <Modal.Body>
// <div>
// <Legend>{constants.METADATA}</Legend>
// </div>
// {renderInfoItem(
// constants.FILE_ID,
// items[photoSwipe?.getCurrentIndex()]?.id
// )}
// {metadata?.title && (
// <RenderFileName
// shouldDisableEdits={shouldDisableEdits}
// file={items[photoSwipe?.getCurrentIndex()]}
// scheduleUpdate={scheduleUpdate}
// />
// )}
// {metadata?.creationTime && (
// <RenderCreationTime
// shouldDisableEdits={shouldDisableEdits}
// file={items[photoSwipe?.getCurrentIndex()]}
// scheduleUpdate={scheduleUpdate}
// />
// )}
// {metadata?.modificationTime &&
// renderInfoItem(
// constants.UPDATED_ON,
// formatDateTime(metadata.modificationTime / 1000)
// )}
// {metadata?.longitude > 0 &&
// metadata?.longitude > 0 &&
// renderInfoItem(
// constants.LOCATION,
// <a
// href={`https://www.openstreetmap.org/?mlat=${metadata.latitude}&mlon=${metadata.longitude}#map=15/${metadata.latitude}/${metadata.longitude}`}
// target="_blank"
// rel="noopener noreferrer">
// {constants.SHOW_MAP}
// </a>
// )}
// {/* {appContext.mlSearchEnabled && ( */}
// <>
// <div>
// <Legend>{constants.PEOPLE}</Legend>
// </div>
// <PhotoPeopleList
// file={items[photoSwipe?.getCurrentIndex()]}
// updateMLDataIndex={updateMLDataIndex}
// />
// <div>
// <Legend>{constants.UNIDENTIFIED_FACES}</Legend>
// </div>
// <UnidentifiedFaces
// file={items[photoSwipe?.getCurrentIndex()]}
// updateMLDataIndex={updateMLDataIndex}
// />
// <div>
// <Legend>{constants.OBJECTS}</Legend>
// <ObjectLabelList
// file={items[photoSwipe?.getCurrentIndex()]}
// updateMLDataIndex={updateMLDataIndex}
// />
// </div>
// <div>
// <Legend>{constants.TEXT}</Legend>
// <WordList
// file={items[photoSwipe?.getCurrentIndex()]}
// updateMLDataIndex={updateMLDataIndex}
// />
// </div>
// <MLServiceFileInfoButton
// file={items[photoSwipe?.getCurrentIndex()]}
// updateMLDataIndex={updateMLDataIndex}
// setUpdateMLDataIndex={setUpdateMLDataIndex}
// />
// </>
// {/* )} */}
// {exif && (
// <>
// <ExifData exif={exif} />
// </>
// )}
// </Modal.Body>
// <Modal.Footer>
// <Button variant="outline-secondary" onClick={handleCloseInfo}>
// {constants.CLOSE}
// </Button>
// </Modal.Footer>
// </Modal>
// );
// }
// function PhotoSwipe(props: Iprops) {
// const pswpElement = useRef<HTMLDivElement>();
// const [photoSwipe, setPhotoSwipe] = useState<Photoswipe<any>>();
// const { isOpen, items } = props;
// const [isFav, setIsFav] = useState(false);
// const [showInfo, setShowInfo] = useState(false);
// const [metadata, setMetaData] = useState<EnteFile['metadata']>(null);
// const [exif, setExif] = useState<any>(null);
// const needUpdate = useRef(false);
// const publicCollectionGalleryContext = useContext(
// PublicCollectionGalleryContext
// );
// const galleryContext = useContext(GalleryContext);
// useEffect(() => {
// if (!pswpElement) return;
// if (isOpen) {
// openPhotoSwipe();
// }
// if (!isOpen) {
// closePhotoSwipe();
// }
// return () => {
// closePhotoSwipe();
// };
// }, [isOpen]);
// useEffect(() => {
// updateItems(items);
// }, [items]);
// // useEffect(() => {
// // if (photoSwipe) {
// // photoSwipe.options.arrowKeys = !showInfo;
// // photoSwipe.options.escKey = !showInfo;
// // }
// // }, [showInfo]);
// function updateFavButton() {
// setIsFav(isInFav(this?.currItem));
// }
// const openPhotoSwipe = () => {
// const { items, currentIndex } = props;
// const options = {
// history: false,
// maxSpreadZoom: 5,
// index: currentIndex,
// showHideOpacity: true,
// getDoubleTapZoom(isMouseClick, item) {
// if (isMouseClick) {
// return 2.5;
// }
// // zoom to original if initial zoom is less than 0.7x,
// // otherwise to 1.5x, to make sure that double-tap gesture always zooms image
// return item.initialZoomLevel < 0.7 ? 1 : 1.5;
// },
// getThumbBoundsFn: (index) => {
// try {
// const file = items[index];
// const ele = document.getElementById(`thumb-${file.id}`);
// if (ele) {
// const rect = ele.getBoundingClientRect();
// const pageYScroll =
// window.pageYOffset ||
// document.documentElement.scrollTop;
// return {
// x: rect.left,
// y: rect.top + pageYScroll,
// w: rect.width,
// };
// }
// return null;
// } catch (e) {
// return null;
// }
// },
// };
// const photoSwipe = new Photoswipe(
// pswpElement.current,
// PhotoswipeUIDefault,
// items,
// options
// );
// events.forEach((event) => {
// const callback = props[event];
// if (callback || event === 'destroy') {
// photoSwipe.listen(event, function (...args) {
// if (callback) {
// args.unshift(this);
// callback(...args);
// }
// if (event === 'destroy') {
// handleClose();
// }
// if (event === 'close') {
// handleClose();
// }
// });
// }
// });
// photoSwipe.listen('beforeChange', function () {
// updateInfo.call(this);
// updateFavButton.call(this);
// });
// photoSwipe.listen('resize', checkExifAvailable);
// photoSwipe.init();
// needUpdate.current = false;
// setPhotoSwipe(photoSwipe);
// };
// const closePhotoSwipe = () => {
// if (photoSwipe) photoSwipe.close();
// };
// const handleClose = () => {
// const { onClose } = props;
// if (typeof onClose === 'function') {
// onClose(needUpdate.current);
// }
// const videoTags = document.getElementsByTagName('video');
// for (const videoTag of videoTags) {
// videoTag.pause();
// }
// handleCloseInfo();
// };
// const isInFav = (file) => {
// const { favItemIds } = props;
// if (favItemIds && file) {
// return favItemIds.has(file.id);
// }
// return false;
// };
// const onFavClick = async (file) => {
// const { favItemIds } = props;
// if (!isInFav(file)) {
// favItemIds.add(file.id);
// addToFavorites(file);
// setIsFav(true);
// } else {
// favItemIds.delete(file.id);
// removeFromFavorites(file);
// setIsFav(false);
// }
// needUpdate.current = true;
// };
// const updateItems = (items = []) => {
// if (photoSwipe) {
// photoSwipe.items.length = 0;
// items.forEach((item) => {
// photoSwipe.items.push(item);
// });
// photoSwipe.invalidateCurrItems();
// // photoSwipe.updateSize(true);
// }
// };
// const checkExifAvailable = async () => {
// setExif(null);
// await sleep(100);
// try {
// const img: HTMLImageElement = document.querySelector(
// '.pswp__img:not(.pswp__img--placeholder)'
// );
// if (img) {
// const exifData = await exifr.parse(img);
// if (!exifData) {
// return;
// }
// exifData.raw = prettyPrintExif(exifData);
// setExif(exifData);
// }
// } catch (e) {
// logError(e, 'exifr parsing failed');
// }
// };
// function updateInfo() {
// const file: EnteFile = this?.currItem;
// if (file?.metadata) {
// setMetaData(file.metadata);
// setExif(null);
// checkExifAvailable();
// }
// }
// const handleCloseInfo = () => {
// setShowInfo(false);
// };
// const handleOpenInfo = () => {
// setShowInfo(true);
// };
// const downloadFileHelper = async (file) => {
// galleryContext.startLoading();
// await downloadFile(
// file,
// publicCollectionGalleryContext.accessedThroughSharedURL,
// publicCollectionGalleryContext.token
// );
// galleryContext.finishLoading();
// };
// const scheduleUpdate = () => (needUpdate.current = true);
// const { id } = props;
// let { className } = props;
// className = classnames(['pswp', className]).trim();
// return (
// <>
// <div
// id={id}
// className={className}
// tabIndex={Number('-1')}
// role="dialog"
// aria-hidden="true"
// ref={pswpElement}>
// <div className="pswp__bg" />
// <div className="pswp__scroll-wrap">
// <div className="pswp__container">
// <div className="pswp__item" />
// <div className="pswp__item" />
// <div className="pswp__item" />
// </div>
// <div className="pswp__ui pswp__ui--hidden">
// <div className="pswp__top-bar">
// <div className="pswp__counter" />
// <button
// className="pswp__button pswp__button--close"
// title={constants.CLOSE}
// />
// <button
// className="pswp-custom download-btn"
// title={constants.DOWNLOAD}
// onClick={() =>
// downloadFileHelper(photoSwipe.currItem)
// }
// />
// <button
// className="pswp__button pswp__button--fs"
// title={constants.TOGGLE_FULLSCREEN}
// />
// <button
// className="pswp__button pswp__button--zoom"
// title={constants.ZOOM_IN_OUT}
// />
// {!props.isSharedCollection &&
// !props.isTrashCollection && (
// <FavButton
// size={44}
// isClick={isFav}
// onClick={() => {
// onFavClick(photoSwipe?.currItem);
// }}
// />
// )}
// <button
// className="pswp-custom info-btn"
// title={constants.INFO}
// onClick={handleOpenInfo}
// />
// <div className="pswp__preloader">
// <div className="pswp__preloader__icn">
// <div className="pswp__preloader__cut">
// <div className="pswp__preloader__donut" />
// </div>
// </div>
// </div>
// </div>
// <div className="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
// <div className="pswp__share-tooltip" />
// </div>
// <button
// className="pswp__button pswp__button--arrow--left"
// title={constants.PREVIOUS}
// />
// <button
// className="pswp__button pswp__button--arrow--right"
// title={constants.NEXT}
// />
// <div className="pswp__caption">
// <div />
// </div>
// </div>
// </div>
// </div>
// <InfoModal
// shouldDisableEdits={props.isSharedCollection}
// showInfo={showInfo}
// handleCloseInfo={handleCloseInfo}
// items={items}
// photoSwipe={photoSwipe}
// metadata={metadata}
// exif={exif}
// scheduleUpdate={scheduleUpdate}
// />
// </>
// );
// }
// export default PhotoSwipe;

View file

@ -1,831 +0,0 @@
import React, { useContext, useEffect, useRef, useState } from 'react';
import Photoswipe from 'photoswipe';
import PhotoswipeUIDefault from 'photoswipe/dist/photoswipe-ui-default';
import classnames from 'classnames';
import FavButton from 'components/FavButton';
import {
addToFavorites,
removeFromFavorites,
} from 'services/collectionService';
import { updatePublicMagicMetadata } from 'services/fileService';
import { EnteFile } from 'types/file';
import constants from 'utils/strings/constants';
import exifr from 'exifr';
import Modal from 'react-bootstrap/Modal';
import Button from 'react-bootstrap/Button';
import styled from 'styled-components';
import events from './events';
import {
changeFileCreationTime,
changeFileName,
downloadFile,
formatDateTime,
splitFilenameAndExtension,
updateExistingFilePubMetadata,
} from 'utils/file';
import { Col, Form, FormCheck, FormControl } from 'react-bootstrap';
import { prettyPrintExif } from 'utils/exif';
import EditIcon from 'components/icons/EditIcon';
import {
FlexWrapper,
FreeFlowText,
IconButton,
Label,
Row,
Value,
} from 'components/Container';
import { logError } from 'utils/sentry';
import CloseIcon from 'components/icons/CloseIcon';
import TickIcon from 'components/icons/TickIcon';
import {
PhotoPeopleList,
UnidentifiedFaces,
} from 'components/MachineLearning/PeopleList';
import { Formik } from 'formik';
import * as Yup from 'yup';
import EnteSpinner from 'components/EnteSpinner';
import EnteDateTimePicker from 'components/EnteDateTimePicker';
// import { AppContext } from 'pages/_app';
import { MAX_EDITED_FILE_NAME_LENGTH } from 'constants/file';
import { sleep } from 'utils/common';
import { PublicCollectionGalleryContext } from 'utils/publicCollectionGallery';
import { GalleryContext } from 'pages/gallery';
import { ObjectLabelList } from 'components/MachineLearning/ObjectList';
import { WordList } from 'components/MachineLearning/WordList';
import MLServiceFileInfoButton from 'components/MachineLearning/MLServiceFileInfoButton';
const SmallLoadingSpinner = () => (
<EnteSpinner
style={{
width: '20px',
height: '20px',
}}
/>
);
interface Iprops {
isOpen: boolean;
items: EnteFile[];
currentIndex?: number;
onClose?: (needUpdate: boolean) => void;
gettingData: (instance: any, index: number, item: EnteFile) => void;
id?: string;
className?: string;
favItemIds: Set<number>;
isSharedCollection: boolean;
isTrashCollection: boolean;
}
const LegendContainer = styled.div`
display: flex;
justify-content: space-between;
`;
const Legend = styled.span`
font-size: 20px;
color: #ddd;
display: inline;
`;
const Pre = styled.pre`
color: #aaa;
padding: 7px 15px;
`;
const renderInfoItem = (label: string, value: string | JSX.Element) => (
<Row>
<Label width="30%">{label}</Label>
<Value width="70%">{value}</Value>
</Row>
);
function RenderCreationTime({
shouldDisableEdits,
file,
scheduleUpdate,
}: {
shouldDisableEdits: boolean;
file: EnteFile;
scheduleUpdate: () => void;
}) {
const [loading, setLoading] = useState(false);
const originalCreationTime = new Date(file?.metadata.creationTime / 1000);
const [isInEditMode, setIsInEditMode] = useState(false);
const [pickedTime, setPickedTime] = useState(originalCreationTime);
const openEditMode = () => setIsInEditMode(true);
const closeEditMode = () => setIsInEditMode(false);
const saveEdits = async () => {
try {
setLoading(true);
if (isInEditMode && file) {
const unixTimeInMicroSec = pickedTime.getTime() * 1000;
if (unixTimeInMicroSec === file?.metadata.creationTime) {
closeEditMode();
return;
}
let updatedFile = await changeFileCreationTime(
file,
unixTimeInMicroSec
);
updatedFile = (
await updatePublicMagicMetadata([updatedFile])
)[0];
updateExistingFilePubMetadata(file, updatedFile);
scheduleUpdate();
}
} catch (e) {
logError(e, 'failed to update creationTime');
} finally {
closeEditMode();
setLoading(false);
}
};
const discardEdits = () => {
setPickedTime(originalCreationTime);
closeEditMode();
};
const handleChange = (newDate: Date) => {
if (newDate instanceof Date) {
setPickedTime(newDate);
}
};
return (
<>
<Row>
<Label width="30%">{constants.CREATION_TIME}</Label>
<Value width={isInEditMode ? '50%' : '60%'}>
{isInEditMode ? (
<EnteDateTimePicker
loading={loading}
isInEditMode={isInEditMode}
pickedTime={pickedTime}
handleChange={handleChange}
/>
) : (
formatDateTime(pickedTime)
)}
</Value>
<Value
width={isInEditMode ? '20%' : '10%'}
style={{ cursor: 'pointer', marginLeft: '10px' }}>
{!shouldDisableEdits &&
(!isInEditMode ? (
<IconButton onClick={openEditMode}>
<EditIcon />
</IconButton>
) : (
<>
<IconButton onClick={saveEdits}>
{loading ? (
<SmallLoadingSpinner />
) : (
<TickIcon />
)}
</IconButton>
<IconButton onClick={discardEdits}>
<CloseIcon />
</IconButton>
</>
))}
</Value>
</Row>
</>
);
}
const getFileTitle = (filename, extension) => {
if (extension) {
return filename + '.' + extension;
} else {
return filename;
}
};
interface formValues {
filename: string;
}
const FileNameEditForm = ({ filename, saveEdits, discardEdits, extension }) => {
const [loading, setLoading] = useState(false);
const onSubmit = async (values: formValues) => {
try {
setLoading(true);
await saveEdits(values.filename);
} finally {
setLoading(false);
}
};
return (
<Formik<formValues>
initialValues={{ filename }}
validationSchema={Yup.object().shape({
filename: Yup.string()
.required(constants.REQUIRED)
.max(
MAX_EDITED_FILE_NAME_LENGTH,
constants.FILE_NAME_CHARACTER_LIMIT
),
})}
validateOnBlur={false}
onSubmit={onSubmit}>
{({ values, errors, handleChange, handleSubmit }) => (
<Form noValidate onSubmit={handleSubmit}>
<Form.Row>
<Form.Group
bsPrefix="ente-form-group"
as={Col}
xs={extension ? 7 : 8}>
<Form.Control
as="textarea"
placeholder={constants.FILE_NAME}
value={values.filename}
onChange={handleChange('filename')}
isInvalid={Boolean(errors.filename)}
autoFocus
disabled={loading}
/>
<FormControl.Feedback
type="invalid"
style={{ textAlign: 'center' }}>
{errors.filename}
</FormControl.Feedback>
</Form.Group>
{extension && (
<Form.Group
bsPrefix="ente-form-group"
as={Col}
xs={1}
controlId="formHorizontalFileName">
<FlexWrapper style={{ padding: '5px' }}>
{`.${extension}`}
</FlexWrapper>
</Form.Group>
)}
<Form.Group bsPrefix="ente-form-group" as={Col} xs={2}>
<Value width={'16.67%'}>
<IconButton type="submit" disabled={loading}>
{loading ? (
<SmallLoadingSpinner />
) : (
<TickIcon />
)}
</IconButton>
<IconButton
onClick={discardEdits}
disabled={loading}>
<CloseIcon />
</IconButton>
</Value>
</Form.Group>
</Form.Row>
</Form>
)}
</Formik>
);
};
function RenderFileName({
shouldDisableEdits,
file,
scheduleUpdate,
}: {
shouldDisableEdits: boolean;
file: EnteFile;
scheduleUpdate: () => void;
}) {
const originalTitle = file?.metadata.title;
const [isInEditMode, setIsInEditMode] = useState(false);
const [originalFileName, extension] =
splitFilenameAndExtension(originalTitle);
const [filename, setFilename] = useState(originalFileName);
const openEditMode = () => setIsInEditMode(true);
const closeEditMode = () => setIsInEditMode(false);
const saveEdits = async (newFilename: string) => {
try {
if (file) {
if (filename === newFilename) {
closeEditMode();
return;
}
setFilename(newFilename);
const newTitle = getFileTitle(newFilename, extension);
let updatedFile = await changeFileName(file, newTitle);
updatedFile = (
await updatePublicMagicMetadata([updatedFile])
)[0];
updateExistingFilePubMetadata(file, updatedFile);
scheduleUpdate();
}
} catch (e) {
logError(e, 'failed to update file name');
} finally {
closeEditMode();
}
};
return (
<>
<Row>
<Label width="30%">{constants.FILE_NAME}</Label>
{!isInEditMode ? (
<>
<Value width="60%">
<FreeFlowText>
{getFileTitle(filename, extension)}
</FreeFlowText>
</Value>
{!shouldDisableEdits && (
<Value
width="10%"
style={{
cursor: 'pointer',
marginLeft: '10px',
}}>
<IconButton onClick={openEditMode}>
<EditIcon />
</IconButton>
</Value>
)}
</>
) : (
<FileNameEditForm
extension={extension}
filename={filename}
saveEdits={saveEdits}
discardEdits={closeEditMode}
/>
)}
</Row>
</>
);
}
function ExifData(props: { exif: any }) {
const { exif } = props;
const [showAll, setShowAll] = useState(false);
const changeHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
setShowAll(e.target.checked);
};
const renderAllValues = () => <Pre>{exif.raw}</Pre>;
const renderSelectedValues = () => (
<>
{exif?.Make &&
exif?.Model &&
renderInfoItem(constants.DEVICE, `${exif.Make} ${exif.Model}`)}
{exif?.ImageWidth &&
exif?.ImageHeight &&
renderInfoItem(
constants.IMAGE_SIZE,
`${exif.ImageWidth} x ${exif.ImageHeight}`
)}
{exif?.Flash && renderInfoItem(constants.FLASH, exif.Flash)}
{exif?.FocalLength &&
renderInfoItem(
constants.FOCAL_LENGTH,
exif.FocalLength.toString()
)}
{exif?.ApertureValue &&
renderInfoItem(
constants.APERTURE,
exif.ApertureValue.toString()
)}
{exif?.ISOSpeedRatings &&
renderInfoItem(constants.ISO, exif.ISOSpeedRatings.toString())}
</>
);
return (
<>
<LegendContainer>
<Legend>{constants.EXIF}</Legend>
<FormCheck>
<FormCheck.Label>
<FormCheck.Input onChange={changeHandler} />
{constants.SHOW_ALL}
</FormCheck.Label>
</FormCheck>
</LegendContainer>
{showAll ? renderAllValues() : renderSelectedValues()}
</>
);
}
function InfoModal({
shouldDisableEdits,
showInfo,
handleCloseInfo,
items,
photoSwipe,
metadata,
exif,
scheduleUpdate,
}) {
// const appContext = useContext(AppContext);
const [updateMLDataIndex, setUpdateMLDataIndex] = useState(0);
return (
<Modal show={showInfo} onHide={handleCloseInfo}>
<Modal.Header closeButton>
<Modal.Title>{constants.INFO}</Modal.Title>
</Modal.Header>
<Modal.Body>
<div>
<Legend>{constants.METADATA}</Legend>
</div>
{renderInfoItem(
constants.FILE_ID,
items[photoSwipe?.getCurrentIndex()]?.id
)}
{metadata?.title && (
<RenderFileName
shouldDisableEdits={shouldDisableEdits}
file={items[photoSwipe?.getCurrentIndex()]}
scheduleUpdate={scheduleUpdate}
/>
)}
{metadata?.creationTime && (
<RenderCreationTime
shouldDisableEdits={shouldDisableEdits}
file={items[photoSwipe?.getCurrentIndex()]}
scheduleUpdate={scheduleUpdate}
/>
)}
{metadata?.modificationTime &&
renderInfoItem(
constants.UPDATED_ON,
formatDateTime(metadata.modificationTime / 1000)
)}
{metadata?.longitude > 0 &&
metadata?.longitude > 0 &&
renderInfoItem(
constants.LOCATION,
<a
href={`https://www.openstreetmap.org/?mlat=${metadata.latitude}&mlon=${metadata.longitude}#map=15/${metadata.latitude}/${metadata.longitude}`}
target="_blank"
rel="noopener noreferrer">
{constants.SHOW_MAP}
</a>
)}
{/* {appContext.mlSearchEnabled && ( */}
<>
<div>
<Legend>{constants.PEOPLE}</Legend>
</div>
<PhotoPeopleList
file={items[photoSwipe?.getCurrentIndex()]}
updateMLDataIndex={updateMLDataIndex}
/>
<div>
<Legend>{constants.UNIDENTIFIED_FACES}</Legend>
</div>
<UnidentifiedFaces
file={items[photoSwipe?.getCurrentIndex()]}
updateMLDataIndex={updateMLDataIndex}
/>
<div>
<Legend>{constants.OBJECTS}</Legend>
<ObjectLabelList
file={items[photoSwipe?.getCurrentIndex()]}
updateMLDataIndex={updateMLDataIndex}
/>
</div>
<div>
<Legend>{constants.TEXT}</Legend>
<WordList
file={items[photoSwipe?.getCurrentIndex()]}
updateMLDataIndex={updateMLDataIndex}
/>
</div>
<MLServiceFileInfoButton
file={items[photoSwipe?.getCurrentIndex()]}
updateMLDataIndex={updateMLDataIndex}
setUpdateMLDataIndex={setUpdateMLDataIndex}
/>
</>
{/* )} */}
{exif && (
<>
<ExifData exif={exif} />
</>
)}
</Modal.Body>
<Modal.Footer>
<Button variant="outline-secondary" onClick={handleCloseInfo}>
{constants.CLOSE}
</Button>
</Modal.Footer>
</Modal>
);
}
function PhotoSwipe(props: Iprops) {
const pswpElement = useRef<HTMLDivElement>();
const [photoSwipe, setPhotoSwipe] = useState<Photoswipe<any>>();
const { isOpen, items } = props;
const [isFav, setIsFav] = useState(false);
const [showInfo, setShowInfo] = useState(false);
const [metadata, setMetaData] = useState<EnteFile['metadata']>(null);
const [exif, setExif] = useState<any>(null);
const needUpdate = useRef(false);
const publicCollectionGalleryContext = useContext(
PublicCollectionGalleryContext
);
const galleryContext = useContext(GalleryContext);
useEffect(() => {
if (!pswpElement) return;
if (isOpen) {
openPhotoSwipe();
}
if (!isOpen) {
closePhotoSwipe();
}
return () => {
closePhotoSwipe();
};
}, [isOpen]);
useEffect(() => {
updateItems(items);
}, [items]);
// useEffect(() => {
// if (photoSwipe) {
// photoSwipe.options.arrowKeys = !showInfo;
// photoSwipe.options.escKey = !showInfo;
// }
// }, [showInfo]);
function updateFavButton() {
setIsFav(isInFav(this?.currItem));
}
const openPhotoSwipe = () => {
const { items, currentIndex } = props;
const options = {
history: false,
maxSpreadZoom: 5,
index: currentIndex,
showHideOpacity: true,
getDoubleTapZoom(isMouseClick, item) {
if (isMouseClick) {
return 2.5;
}
// zoom to original if initial zoom is less than 0.7x,
// otherwise to 1.5x, to make sure that double-tap gesture always zooms image
return item.initialZoomLevel < 0.7 ? 1 : 1.5;
},
getThumbBoundsFn: (index) => {
try {
const file = items[index];
const ele = document.getElementById(`thumb-${file.id}`);
if (ele) {
const rect = ele.getBoundingClientRect();
const pageYScroll =
window.pageYOffset ||
document.documentElement.scrollTop;
return {
x: rect.left,
y: rect.top + pageYScroll,
w: rect.width,
};
}
return null;
} catch (e) {
return null;
}
},
};
const photoSwipe = new Photoswipe(
pswpElement.current,
PhotoswipeUIDefault,
items,
options
);
events.forEach((event) => {
const callback = props[event];
if (callback || event === 'destroy') {
photoSwipe.listen(event, function (...args) {
if (callback) {
args.unshift(this);
callback(...args);
}
if (event === 'destroy') {
handleClose();
}
if (event === 'close') {
handleClose();
}
});
}
});
photoSwipe.listen('beforeChange', function () {
updateInfo.call(this);
updateFavButton.call(this);
});
photoSwipe.listen('resize', checkExifAvailable);
photoSwipe.init();
needUpdate.current = false;
setPhotoSwipe(photoSwipe);
};
const closePhotoSwipe = () => {
if (photoSwipe) photoSwipe.close();
};
const handleClose = () => {
const { onClose } = props;
if (typeof onClose === 'function') {
onClose(needUpdate.current);
}
const videoTags = document.getElementsByTagName('video');
for (const videoTag of videoTags) {
videoTag.pause();
}
handleCloseInfo();
};
const isInFav = (file) => {
const { favItemIds } = props;
if (favItemIds && file) {
return favItemIds.has(file.id);
}
return false;
};
const onFavClick = async (file) => {
const { favItemIds } = props;
if (!isInFav(file)) {
favItemIds.add(file.id);
addToFavorites(file);
setIsFav(true);
} else {
favItemIds.delete(file.id);
removeFromFavorites(file);
setIsFav(false);
}
needUpdate.current = true;
};
const updateItems = (items = []) => {
if (photoSwipe) {
photoSwipe.items.length = 0;
items.forEach((item) => {
photoSwipe.items.push(item);
});
photoSwipe.invalidateCurrItems();
// photoSwipe.updateSize(true);
}
};
const checkExifAvailable = async () => {
setExif(null);
await sleep(100);
try {
const img: HTMLImageElement = document.querySelector(
'.pswp__img:not(.pswp__img--placeholder)'
);
if (img) {
const exifData = await exifr.parse(img);
if (!exifData) {
return;
}
exifData.raw = prettyPrintExif(exifData);
setExif(exifData);
}
} catch (e) {
logError(e, 'exifr parsing failed');
}
};
function updateInfo() {
const file: EnteFile = this?.currItem;
if (file?.metadata) {
setMetaData(file.metadata);
setExif(null);
checkExifAvailable();
}
}
const handleCloseInfo = () => {
setShowInfo(false);
};
const handleOpenInfo = () => {
setShowInfo(true);
};
const downloadFileHelper = async (file) => {
galleryContext.startLoading();
await downloadFile(
file,
publicCollectionGalleryContext.accessedThroughSharedURL,
publicCollectionGalleryContext.token
);
galleryContext.finishLoading();
};
const scheduleUpdate = () => (needUpdate.current = true);
const { id } = props;
let { className } = props;
className = classnames(['pswp', className]).trim();
return (
<>
<div
id={id}
className={className}
tabIndex={Number('-1')}
role="dialog"
aria-hidden="true"
ref={pswpElement}>
<div className="pswp__bg" />
<div className="pswp__scroll-wrap">
<div className="pswp__container">
<div className="pswp__item" />
<div className="pswp__item" />
<div className="pswp__item" />
</div>
<div className="pswp__ui pswp__ui--hidden">
<div className="pswp__top-bar">
<div className="pswp__counter" />
<button
className="pswp__button pswp__button--close"
title={constants.CLOSE}
/>
<button
className="pswp-custom download-btn"
title={constants.DOWNLOAD}
onClick={() =>
downloadFileHelper(photoSwipe.currItem)
}
/>
<button
className="pswp__button pswp__button--fs"
title={constants.TOGGLE_FULLSCREEN}
/>
<button
className="pswp__button pswp__button--zoom"
title={constants.ZOOM_IN_OUT}
/>
{!props.isSharedCollection &&
!props.isTrashCollection && (
<FavButton
size={44}
isClick={isFav}
onClick={() => {
onFavClick(photoSwipe?.currItem);
}}
/>
)}
<button
className="pswp-custom info-btn"
title={constants.INFO}
onClick={handleOpenInfo}
/>
<div className="pswp__preloader">
<div className="pswp__preloader__icn">
<div className="pswp__preloader__cut">
<div className="pswp__preloader__donut" />
</div>
</div>
</div>
</div>
<div className="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
<div className="pswp__share-tooltip" />
</div>
<button
className="pswp__button pswp__button--arrow--left"
title={constants.PREVIOUS}
/>
<button
className="pswp__button pswp__button--arrow--right"
title={constants.NEXT}
/>
<div className="pswp__caption">
<div />
</div>
</div>
</div>
</div>
<InfoModal
shouldDisableEdits={props.isSharedCollection}
showInfo={showInfo}
handleCloseInfo={handleCloseInfo}
items={items}
photoSwipe={photoSwipe}
metadata={metadata}
exif={exif}
scheduleUpdate={scheduleUpdate}
/>
</>
);
}
export default PhotoSwipe;

View file

@ -0,0 +1,496 @@
export {};
// import React, { useContext, useEffect, useRef, useState } from 'react';
// import styled from 'styled-components';
// import AsyncSelect from 'react-select/async';
// import { components } from 'react-select';
// import debounce from 'debounce-promise';
// import {
// getAllPeopleSuggestion,
// getHolidaySuggestion,
// getIndexStatusSuggestion,
// getYearSuggestion,
// parseHumanDate,
// searchCollection,
// searchFiles,
// searchLocation,
// searchText,
// searchThing,
// } from 'services/searchService';
// import { getFormattedDate, isInsideBox } from 'utils/search';
// import constants from 'utils/strings/constants';
// import LocationIcon from './icons/LocationIcon';
// import DateIcon from './icons/DateIcon';
// import SearchIcon from './icons/SearchIcon';
// import CloseIcon from './icons/CloseIcon';
// import { Collection } from 'types/collection';
// import CollectionIcon from './icons/CollectionIcon';
// import ImageIcon from './icons/ImageIcon';
// import VideoIcon from './icons/VideoIcon';
// import { IconButton, Row } from './Container';
// import { EnteFile } from 'types/file';
// import { Suggestion, SuggestionType, DateValue, Bbox } from 'types/search';
// import { Search, SearchStats } from 'types/gallery';
// import { FILE_TYPE } from 'constants/file';
// import { GalleryContext } from 'pages/gallery';
// import { AppContext } from 'pages/_app';
// import { Col } from 'react-bootstrap';
// import { Person, ThingClass, WordGroup } from 'types/machineLearning';
// import { IndexStatus } from 'types/machineLearning/ui';
// import { PeopleList } from './MachineLearning/PeopleList';
// import ObjectIcon from './icons/ObjectIcon';
// import TextIcon from './icons/TextIcon';
// const Wrapper = styled.div<{ isDisabled: boolean; isOpen: boolean }>`
// position: fixed;
// top: 0;
// z-index: 1000;
// display: ${({ isOpen }) => (isOpen ? 'flex' : 'none')};
// width: 100%;
// background: #111;
// @media (min-width: 625px) {
// display: flex;
// width: calc(100vw - 140px);
// margin: 0 70px;
// }
// align-items: center;
// min-height: 64px;
// transition: opacity 1s ease;
// opacity: ${(props) => (props.isDisabled ? 0 : 1)};
// margin-bottom: 10px;
// `;
// const SearchButton = styled.div<{ isOpen: boolean }>`
// display: none;
// @media (max-width: 624px) {
// display: ${({ isOpen }) => (!isOpen ? 'flex' : 'none')};
// right: 80px;
// cursor: pointer;
// position: fixed;
// top: 0;
// z-index: 1000;
// align-items: center;
// min-height: 64px;
// }
// `;
// const SearchStatsContainer = styled.div`
// display: flex;
// justify-content: center;
// align-items: center;
// color: #979797;
// margin-bottom: 8px;
// `;
// const SearchInput = styled.div`
// width: 100%;
// display: flex;
// align-items: center;
// max-width: 484px;
// margin: auto;
// `;
// const Legend = styled.span`
// font-size: 20px;
// color: #ddd;
// display: inline;
// padding: 8px 12px;
// `;
// const Caption = styled.span`
// font-size: 12px;
// display: inline;
// padding: 8px 12px;
// `;
// const LegendRow = styled(Row)`
// align-items: center;
// justify-content: space-between;
// margin-bottom: 0px;
// `;
// interface Props {
// isOpen: boolean;
// isFirstFetch: boolean;
// setOpen: (value: boolean) => void;
// setSearch: (search: Search) => void;
// searchStats: SearchStats;
// collections: Collection[];
// setActiveCollection: (id: number) => void;
// files: EnteFile[];
// }
// export default function SearchBar(props: Props) {
// const selectRef = useRef(null);
// const [value, setValue] = useState<Suggestion>(null);
// const appContext = useContext(AppContext);
// const galleryContext = useContext(GalleryContext);
// const handleChange = (value) => {
// setValue(value);
// };
// // TODO: HACK as AsyncSelect does not support default options reloading on focus/click
// // unwanted side effect: placeholder is not shown on focus/click
// // https://github.com/JedWatson/react-select/issues/1879
// // for correct fix AsyncSelect can be extended to support default options reloading on focus/click
// const handleOnFocus = () => {
// if (appContext.mlSearchEnabled) {
// const emptySearch = ' ';
// selectRef.current.state.inputValue = emptySearch;
// selectRef.current.select.state.inputValue = emptySearch;
// selectRef.current.handleInputChange(emptySearch);
// }
// };
// useEffect(() => search(value), [value]);
// // = =========================
// // Functionality
// // = =========================
// const getAutoCompleteSuggestions = async (searchPhrase: string) => {
// const options: Array<Suggestion> = [];
// searchPhrase = searchPhrase.trim().toLowerCase();
// if (appContext.mlSearchEnabled) {
// options.push(await getIndexStatusSuggestion());
// options.push(...(await getAllPeopleSuggestion()));
// }
// if (!searchPhrase?.length) {
// return options;
// }
// options.push(...getHolidaySuggestion(searchPhrase));
// options.push(...getYearSuggestion(searchPhrase));
// const searchedDates = parseHumanDate(searchPhrase);
// options.push(
// ...searchedDates.map((searchedDate) => ({
// type: SuggestionType.DATE,
// value: searchedDate,
// label: getFormattedDate(searchedDate),
// }))
// );
// const collectionResults = searchCollection(
// searchPhrase,
// props.collections
// );
// options.push(
// ...collectionResults.map(
// (searchResult) =>
// ({
// type: SuggestionType.COLLECTION,
// value: searchResult.id,
// label: searchResult.name,
// } as Suggestion)
// )
// );
// const fileResults = searchFiles(searchPhrase, props.files);
// options.push(
// ...fileResults.map((file) => ({
// type:
// file.type === FILE_TYPE.IMAGE
// ? SuggestionType.IMAGE
// : SuggestionType.VIDEO,
// value: file.index,
// label: file.title,
// }))
// );
// const locationResults = await searchLocation(searchPhrase);
// const filteredLocationWithFiles = locationResults.filter(
// (locationResult) =>
// props.files.find((file) =>
// isInsideBox(file.metadata, locationResult.bbox)
// )
// );
// options.push(
// ...filteredLocationWithFiles.map(
// (searchResult) =>
// ({
// type: SuggestionType.LOCATION,
// value: searchResult.bbox,
// label: searchResult.place,
// } as Suggestion)
// )
// );
// const thingResults = await searchThing(searchPhrase);
// options.push(
// ...thingResults.map(
// (searchResult) =>
// ({
// type: SuggestionType.THING,
// value: searchResult,
// label: searchResult.className,
// } as Suggestion)
// )
// );
// const textResults = await searchText(searchPhrase);
// options.push(
// ...textResults.map(
// (searchResult) =>
// ({
// type: SuggestionType.TEXT,
// value: searchResult,
// label: searchResult.word,
// } as Suggestion)
// )
// );
// return options;
// };
// const getOptions = debounce(getAutoCompleteSuggestions, 250);
// const search = (selectedOption: Suggestion) => {
// // console.log('search...');
// if (!selectedOption) {
// return;
// }
// switch (selectedOption.type) {
// case SuggestionType.DATE:
// props.setSearch({
// date: selectedOption.value as DateValue,
// });
// props.setOpen(true);
// break;
// case SuggestionType.LOCATION:
// props.setSearch({
// location: selectedOption.value as Bbox,
// });
// props.setOpen(true);
// break;
// case SuggestionType.COLLECTION:
// props.setActiveCollection(selectedOption.value as number);
// setValue(null);
// break;
// case SuggestionType.IMAGE:
// case SuggestionType.VIDEO:
// props.setSearch({ fileIndex: selectedOption.value as number });
// setValue(null);
// break;
// case SuggestionType.PERSON:
// props.setSearch({ person: selectedOption.value as Person });
// props.setOpen(true);
// break;
// case SuggestionType.THING:
// props.setSearch({ thing: selectedOption.value as ThingClass });
// props.setOpen(true);
// break;
// case SuggestionType.TEXT:
// props.setSearch({ text: selectedOption.value as WordGroup });
// props.setOpen(true);
// break;
// }
// };
// const resetSearch = () => {
// if (props.isOpen) {
// galleryContext.startLoading();
// props.setSearch({});
// setTimeout(() => {
// galleryContext.finishLoading();
// }, 10);
// props.setOpen(false);
// setValue(null);
// }
// };
// // = =========================
// // UI
// // = =========================
// const getIconByType = (type: SuggestionType) => {
// switch (type) {
// case SuggestionType.DATE:
// return <DateIcon />;
// case SuggestionType.LOCATION:
// return <LocationIcon />;
// case SuggestionType.COLLECTION:
// return <CollectionIcon />;
// case SuggestionType.IMAGE:
// return <ImageIcon />;
// case SuggestionType.VIDEO:
// return <VideoIcon />;
// case SuggestionType.THING:
// return <ObjectIcon />;
// case SuggestionType.TEXT:
// return <TextIcon />;
// default:
// return <SearchIcon />;
// }
// };
// const LabelWithIcon = (props: { type: SuggestionType; label: string }) => (
// <div style={{ display: 'flex', alignItems: 'center' }}>
// <span style={{ paddingRight: '10px', paddingBottom: '4px' }}>
// {getIconByType(props.type)}
// </span>
// <span>{props.label}</span>
// </div>
// );
// const { Option, Control, Menu } = components;
// const OptionWithIcon = (props) =>
// !props.data.hide && (
// <Option {...props}>
// <LabelWithIcon
// type={props.data.type}
// label={props.data.label}
// />
// </Option>
// );
// const ControlWithIcon = (props) => (
// <Control {...props}>
// <span
// className="icon"
// style={{
// paddingLeft: '10px',
// paddingBottom: '4px',
// }}>
// {getIconByType(props.getValue()[0]?.type)}
// </span>
// {props.children}
// </Control>
// );
// const CustomMenu = (props) => {
// // console.log("props.selectProps.options: ", selectRef);
// const peopleSuggestions = props.selectProps.options.filter(
// (o) => o.type === SuggestionType.PERSON
// );
// const people = peopleSuggestions.map((o) => o.value);
// const indexStatusSuggestion = props.selectProps.options.filter(
// (o) => o.type === SuggestionType.INDEX_STATUS
// )[0] as Suggestion;
// const indexStatus = indexStatusSuggestion?.value as IndexStatus;
// return (
// <Menu {...props}>
// {appContext.mlSearchEnabled && (
// <Col>
// <LegendRow>
// <Legend>{constants.PEOPLE}</Legend>
// {indexStatus && (
// <Caption>{indexStatusSuggestion.label}</Caption>
// )}
// </LegendRow>
// {people && people.length > 0 && (
// <Row>
// <PeopleList
// people={people}
// maxRows={2}
// onSelect={(person, index) => {
// selectRef.current.blur();
// setValue(peopleSuggestions[index]);
// }}></PeopleList>
// </Row>
// )}
// </Col>
// )}
// {props.children}
// </Menu>
// );
// };
// const customStyles = {
// control: (style, { isFocused }) => ({
// ...style,
// backgroundColor: '#282828',
// color: '#d1d1d1',
// borderColor: isFocused ? '#51cd7c' : '#444',
// boxShadow: 'none',
// ':hover': {
// borderColor: '#51cd7c',
// cursor: 'text',
// '&>.icon': { color: '#51cd7c' },
// },
// }),
// input: (style) => ({
// ...style,
// color: '#d1d1d1',
// }),
// menu: (style) => ({
// ...style,
// marginTop: '10px',
// backgroundColor: '#282828',
// }),
// option: (style, { isFocused }) => ({
// ...style,
// backgroundColor: isFocused && '#343434',
// }),
// dropdownIndicator: (style) => ({
// ...style,
// display: 'none',
// }),
// indicatorSeparator: (style) => ({
// ...style,
// display: 'none',
// }),
// clearIndicator: (style) => ({
// ...style,
// display: 'none',
// }),
// singleValue: (style, state) => ({
// ...style,
// backgroundColor: '#282828',
// color: '#d1d1d1',
// display: state.selectProps.menuIsOpen ? 'none' : 'block',
// }),
// placeholder: (style) => ({
// ...style,
// color: '#686868',
// wordSpacing: '2px',
// whiteSpace: 'nowrap',
// }),
// };
// return (
// <>
// {props.searchStats && (
// <SearchStatsContainer>
// {constants.SEARCH_STATS(props.searchStats)}
// </SearchStatsContainer>
// )}
// <Wrapper isDisabled={props.isFirstFetch} isOpen={props.isOpen}>
// <SearchInput>
// <div
// style={{
// flex: 1,
// margin: '10px',
// }}>
// <AsyncSelect
// ref={selectRef}
// value={value}
// components={{
// Menu: CustomMenu,
// Option: OptionWithIcon,
// Control: ControlWithIcon,
// }}
// placeholder={constants.SEARCH_HINT()}
// loadOptions={getOptions}
// onChange={handleChange}
// onFocus={handleOnFocus}
// isClearable
// escapeClearsValue
// styles={customStyles}
// noOptionsMessage={() => null}
// />
// </div>
// {props.isOpen && (
// <IconButton onClick={() => resetSearch()}>
// <CloseIcon />
// </IconButton>
// )}
// </SearchInput>
// </Wrapper>
// <SearchButton
// isOpen={props.isOpen}
// onClick={() => !props.isFirstFetch && props.setOpen(true)}>
// <SearchIcon />
// </SearchButton>
// </>
// );
// }

View file

@ -1,495 +0,0 @@
import React, { useContext, useEffect, useRef, useState } from 'react';
import styled from 'styled-components';
import AsyncSelect from 'react-select/async';
import { components } from 'react-select';
import debounce from 'debounce-promise';
import {
getAllPeopleSuggestion,
getHolidaySuggestion,
getIndexStatusSuggestion,
getYearSuggestion,
parseHumanDate,
searchCollection,
searchFiles,
searchLocation,
searchText,
searchThing,
} from 'services/searchService';
import { getFormattedDate, isInsideBox } from 'utils/search';
import constants from 'utils/strings/constants';
import LocationIcon from './icons/LocationIcon';
import DateIcon from './icons/DateIcon';
import SearchIcon from './icons/SearchIcon';
import CloseIcon from './icons/CloseIcon';
import { Collection } from 'types/collection';
import CollectionIcon from './icons/CollectionIcon';
import ImageIcon from './icons/ImageIcon';
import VideoIcon from './icons/VideoIcon';
import { IconButton, Row } from './Container';
import { EnteFile } from 'types/file';
import { Suggestion, SuggestionType, DateValue, Bbox } from 'types/search';
import { Search, SearchStats } from 'types/gallery';
import { FILE_TYPE } from 'constants/file';
import { GalleryContext } from 'pages/gallery';
import { AppContext } from 'pages/_app';
import { Col } from 'react-bootstrap';
import { Person, ThingClass, WordGroup } from 'types/machineLearning';
import { IndexStatus } from 'types/machineLearning/ui';
import { PeopleList } from './MachineLearning/PeopleList';
import ObjectIcon from './icons/ObjectIcon';
import TextIcon from './icons/TextIcon';
const Wrapper = styled.div<{ isDisabled: boolean; isOpen: boolean }>`
position: fixed;
top: 0;
z-index: 1000;
display: ${({ isOpen }) => (isOpen ? 'flex' : 'none')};
width: 100%;
background: #111;
@media (min-width: 625px) {
display: flex;
width: calc(100vw - 140px);
margin: 0 70px;
}
align-items: center;
min-height: 64px;
transition: opacity 1s ease;
opacity: ${(props) => (props.isDisabled ? 0 : 1)};
margin-bottom: 10px;
`;
const SearchButton = styled.div<{ isOpen: boolean }>`
display: none;
@media (max-width: 624px) {
display: ${({ isOpen }) => (!isOpen ? 'flex' : 'none')};
right: 80px;
cursor: pointer;
position: fixed;
top: 0;
z-index: 1000;
align-items: center;
min-height: 64px;
}
`;
const SearchStatsContainer = styled.div`
display: flex;
justify-content: center;
align-items: center;
color: #979797;
margin-bottom: 8px;
`;
const SearchInput = styled.div`
width: 100%;
display: flex;
align-items: center;
max-width: 484px;
margin: auto;
`;
const Legend = styled.span`
font-size: 20px;
color: #ddd;
display: inline;
padding: 8px 12px;
`;
const Caption = styled.span`
font-size: 12px;
display: inline;
padding: 8px 12px;
`;
const LegendRow = styled(Row)`
align-items: center;
justify-content: space-between;
margin-bottom: 0px;
`;
interface Props {
isOpen: boolean;
isFirstFetch: boolean;
setOpen: (value: boolean) => void;
setSearch: (search: Search) => void;
searchStats: SearchStats;
collections: Collection[];
setActiveCollection: (id: number) => void;
files: EnteFile[];
}
export default function SearchBar(props: Props) {
const selectRef = useRef(null);
const [value, setValue] = useState<Suggestion>(null);
const appContext = useContext(AppContext);
const galleryContext = useContext(GalleryContext);
const handleChange = (value) => {
setValue(value);
};
// TODO: HACK as AsyncSelect does not support default options reloading on focus/click
// unwanted side effect: placeholder is not shown on focus/click
// https://github.com/JedWatson/react-select/issues/1879
// for correct fix AsyncSelect can be extended to support default options reloading on focus/click
const handleOnFocus = () => {
if (appContext.mlSearchEnabled) {
const emptySearch = ' ';
selectRef.current.state.inputValue = emptySearch;
selectRef.current.select.state.inputValue = emptySearch;
selectRef.current.handleInputChange(emptySearch);
}
};
useEffect(() => search(value), [value]);
// = =========================
// Functionality
// = =========================
const getAutoCompleteSuggestions = async (searchPhrase: string) => {
const options: Array<Suggestion> = [];
searchPhrase = searchPhrase.trim().toLowerCase();
if (appContext.mlSearchEnabled) {
options.push(await getIndexStatusSuggestion());
options.push(...(await getAllPeopleSuggestion()));
}
if (!searchPhrase?.length) {
return options;
}
options.push(...getHolidaySuggestion(searchPhrase));
options.push(...getYearSuggestion(searchPhrase));
const searchedDates = parseHumanDate(searchPhrase);
options.push(
...searchedDates.map((searchedDate) => ({
type: SuggestionType.DATE,
value: searchedDate,
label: getFormattedDate(searchedDate),
}))
);
const collectionResults = searchCollection(
searchPhrase,
props.collections
);
options.push(
...collectionResults.map(
(searchResult) =>
({
type: SuggestionType.COLLECTION,
value: searchResult.id,
label: searchResult.name,
} as Suggestion)
)
);
const fileResults = searchFiles(searchPhrase, props.files);
options.push(
...fileResults.map((file) => ({
type:
file.type === FILE_TYPE.IMAGE
? SuggestionType.IMAGE
: SuggestionType.VIDEO,
value: file.index,
label: file.title,
}))
);
const locationResults = await searchLocation(searchPhrase);
const filteredLocationWithFiles = locationResults.filter(
(locationResult) =>
props.files.find((file) =>
isInsideBox(file.metadata, locationResult.bbox)
)
);
options.push(
...filteredLocationWithFiles.map(
(searchResult) =>
({
type: SuggestionType.LOCATION,
value: searchResult.bbox,
label: searchResult.place,
} as Suggestion)
)
);
const thingResults = await searchThing(searchPhrase);
options.push(
...thingResults.map(
(searchResult) =>
({
type: SuggestionType.THING,
value: searchResult,
label: searchResult.className,
} as Suggestion)
)
);
const textResults = await searchText(searchPhrase);
options.push(
...textResults.map(
(searchResult) =>
({
type: SuggestionType.TEXT,
value: searchResult,
label: searchResult.word,
} as Suggestion)
)
);
return options;
};
const getOptions = debounce(getAutoCompleteSuggestions, 250);
const search = (selectedOption: Suggestion) => {
// console.log('search...');
if (!selectedOption) {
return;
}
switch (selectedOption.type) {
case SuggestionType.DATE:
props.setSearch({
date: selectedOption.value as DateValue,
});
props.setOpen(true);
break;
case SuggestionType.LOCATION:
props.setSearch({
location: selectedOption.value as Bbox,
});
props.setOpen(true);
break;
case SuggestionType.COLLECTION:
props.setActiveCollection(selectedOption.value as number);
setValue(null);
break;
case SuggestionType.IMAGE:
case SuggestionType.VIDEO:
props.setSearch({ fileIndex: selectedOption.value as number });
setValue(null);
break;
case SuggestionType.PERSON:
props.setSearch({ person: selectedOption.value as Person });
props.setOpen(true);
break;
case SuggestionType.THING:
props.setSearch({ thing: selectedOption.value as ThingClass });
props.setOpen(true);
break;
case SuggestionType.TEXT:
props.setSearch({ text: selectedOption.value as WordGroup });
props.setOpen(true);
break;
}
};
const resetSearch = () => {
if (props.isOpen) {
galleryContext.startLoading();
props.setSearch({});
setTimeout(() => {
galleryContext.finishLoading();
}, 10);
props.setOpen(false);
setValue(null);
}
};
// = =========================
// UI
// = =========================
const getIconByType = (type: SuggestionType) => {
switch (type) {
case SuggestionType.DATE:
return <DateIcon />;
case SuggestionType.LOCATION:
return <LocationIcon />;
case SuggestionType.COLLECTION:
return <CollectionIcon />;
case SuggestionType.IMAGE:
return <ImageIcon />;
case SuggestionType.VIDEO:
return <VideoIcon />;
case SuggestionType.THING:
return <ObjectIcon />;
case SuggestionType.TEXT:
return <TextIcon />;
default:
return <SearchIcon />;
}
};
const LabelWithIcon = (props: { type: SuggestionType; label: string }) => (
<div style={{ display: 'flex', alignItems: 'center' }}>
<span style={{ paddingRight: '10px', paddingBottom: '4px' }}>
{getIconByType(props.type)}
</span>
<span>{props.label}</span>
</div>
);
const { Option, Control, Menu } = components;
const OptionWithIcon = (props) =>
!props.data.hide && (
<Option {...props}>
<LabelWithIcon
type={props.data.type}
label={props.data.label}
/>
</Option>
);
const ControlWithIcon = (props) => (
<Control {...props}>
<span
className="icon"
style={{
paddingLeft: '10px',
paddingBottom: '4px',
}}>
{getIconByType(props.getValue()[0]?.type)}
</span>
{props.children}
</Control>
);
const CustomMenu = (props) => {
// console.log("props.selectProps.options: ", selectRef);
const peopleSuggestions = props.selectProps.options.filter(
(o) => o.type === SuggestionType.PERSON
);
const people = peopleSuggestions.map((o) => o.value);
const indexStatusSuggestion = props.selectProps.options.filter(
(o) => o.type === SuggestionType.INDEX_STATUS
)[0] as Suggestion;
const indexStatus = indexStatusSuggestion?.value as IndexStatus;
return (
<Menu {...props}>
{appContext.mlSearchEnabled && (
<Col>
<LegendRow>
<Legend>{constants.PEOPLE}</Legend>
{indexStatus && (
<Caption>{indexStatusSuggestion.label}</Caption>
)}
</LegendRow>
{people && people.length > 0 && (
<Row>
<PeopleList
people={people}
maxRows={2}
onSelect={(person, index) => {
selectRef.current.blur();
setValue(peopleSuggestions[index]);
}}></PeopleList>
</Row>
)}
</Col>
)}
{props.children}
</Menu>
);
};
const customStyles = {
control: (style, { isFocused }) => ({
...style,
backgroundColor: '#282828',
color: '#d1d1d1',
borderColor: isFocused ? '#51cd7c' : '#444',
boxShadow: 'none',
':hover': {
borderColor: '#51cd7c',
cursor: 'text',
'&>.icon': { color: '#51cd7c' },
},
}),
input: (style) => ({
...style,
color: '#d1d1d1',
}),
menu: (style) => ({
...style,
marginTop: '10px',
backgroundColor: '#282828',
}),
option: (style, { isFocused }) => ({
...style,
backgroundColor: isFocused && '#343434',
}),
dropdownIndicator: (style) => ({
...style,
display: 'none',
}),
indicatorSeparator: (style) => ({
...style,
display: 'none',
}),
clearIndicator: (style) => ({
...style,
display: 'none',
}),
singleValue: (style, state) => ({
...style,
backgroundColor: '#282828',
color: '#d1d1d1',
display: state.selectProps.menuIsOpen ? 'none' : 'block',
}),
placeholder: (style) => ({
...style,
color: '#686868',
wordSpacing: '2px',
whiteSpace: 'nowrap',
}),
};
return (
<>
{props.searchStats && (
<SearchStatsContainer>
{constants.SEARCH_STATS(props.searchStats)}
</SearchStatsContainer>
)}
<Wrapper isDisabled={props.isFirstFetch} isOpen={props.isOpen}>
<SearchInput>
<div
style={{
flex: 1,
margin: '10px',
}}>
<AsyncSelect
ref={selectRef}
value={value}
components={{
Menu: CustomMenu,
Option: OptionWithIcon,
Control: ControlWithIcon,
}}
placeholder={constants.SEARCH_HINT()}
loadOptions={getOptions}
onChange={handleChange}
onFocus={handleOnFocus}
isClearable
escapeClearsValue
styles={customStyles}
noOptionsMessage={() => null}
/>
</div>
{props.isOpen && (
<IconButton onClick={() => resetSearch()}>
<CloseIcon />
</IconButton>
)}
</SearchInput>
</Wrapper>
<SearchButton
isOpen={props.isOpen}
onClick={() => !props.isFirstFetch && props.setOpen(true)}>
<SearchIcon />
</SearchButton>
</>
);
}

View file

@ -0,0 +1,455 @@
export {};
// import React, { useContext, useEffect, useState } from 'react';
// import { slide as Menu } from 'react-burger-menu';
// import constants from 'utils/strings/constants';
// import { getData, LS_KEYS, setData } from 'utils/storage/localStorage';
// import { getToken } from 'utils/common/key';
// import { getEndpoint } from 'utils/common/apiUtil';
// import { Button } from 'react-bootstrap';
// import {
// isSubscriptionActive,
// getUserSubscription,
// isOnFreePlan,
// isSubscriptionCancelled,
// isSubscribed,
// convertToHumanReadable,
// } from 'utils/billing';
// import isElectron from 'is-electron';
// import { Collection } from 'types/collection';
// import { useRouter } from 'next/router';
// import LinkButton from './pages/gallery/LinkButton';
// import { downloadApp } from 'utils/common';
// import { getUserDetails, logoutUser } from 'services/userService';
// import { LogoImage } from 'pages/_app';
// import { SetDialogMessage } from './MessageDialog';
// import EnteSpinner from './EnteSpinner';
// import RecoveryKeyModal from './RecoveryKeyModal';
// import TwoFactorModal from './TwoFactorModal';
// import ExportModal from './ExportModal';
// import { GalleryContext } from 'pages/gallery';
// import InProgressIcon from './icons/InProgressIcon';
// import exportService from 'services/exportService';
// import { Subscription } from 'types/billing';
// import { PAGES } from 'constants/pages';
// import { ARCHIVE_SECTION, TRASH_SECTION } from 'constants/collection';
// import FixLargeThumbnails from './FixLargeThumbnail';
// import { AppContext } from 'pages/_app';
// import { canEnableMlSearch } from 'utils/machineLearning/compatibility';
// import { SetLoading } from 'types/gallery';
// import mlIDbStorage from 'utils/storage/mlIDbStorage';
// interface Props {
// collections: Collection[];
// setDialogMessage: SetDialogMessage;
// setLoading: SetLoading;
// }
// export default function Sidebar(props: Props) {
// const [usage, SetUsage] = useState<string>(null);
// const [user, setUser] = useState(null);
// const [subscription, setSubscription] = useState<Subscription>(null);
// useEffect(() => {
// setUser(getData(LS_KEYS.USER));
// setSubscription(getUserSubscription());
// }, []);
// const [isOpen, setIsOpen] = useState(false);
// const [recoverModalView, setRecoveryModalView] = useState(false);
// const [twoFactorModalView, setTwoFactorModalView] = useState(false);
// const [exportModalView, setExportModalView] = useState(false);
// const [fixLargeThumbsView, setFixLargeThumbsView] = useState(false);
// const galleryContext = useContext(GalleryContext);
// const appContext = useContext(AppContext);
// const enableMlSearch = async () => {
// await appContext.updateMlSearchEnabled(true);
// };
// const disableMlSearch = async () => {
// await appContext.updateMlSearchEnabled(false);
// };
// const clearMLDB = async () => {
// await mlIDbStorage.clearMLDB();
// };
// useEffect(() => {
// const main = async () => {
// if (!isOpen) {
// return;
// }
// const userDetails = await getUserDetails();
// setUser({ ...user, email: userDetails.email });
// SetUsage(convertToHumanReadable(userDetails.usage));
// setSubscription(userDetails.subscription);
// setData(LS_KEYS.USER, {
// ...getData(LS_KEYS.USER),
// email: userDetails.email,
// });
// setData(LS_KEYS.SUBSCRIPTION, userDetails.subscription);
// };
// main();
// }, [isOpen]);
// function openFeedbackURL() {
// const feedbackURL: string = `${getEndpoint()}/users/feedback?token=${encodeURIComponent(
// getToken()
// )}`;
// const win = window.open(feedbackURL, '_blank');
// win.focus();
// }
// function initiateEmail(email: string) {
// const a = document.createElement('a');
// a.href = 'mailto:' + email;
// a.rel = 'noreferrer noopener';
// a.click();
// }
// // eslint-disable-next-line @typescript-eslint/no-unused-vars
// function exportFiles() {
// if (isElectron()) {
// setExportModalView(true);
// } else {
// props.setDialogMessage({
// title: constants.DOWNLOAD_APP,
// content: constants.DOWNLOAD_APP_MESSAGE(),
// staticBackdrop: true,
// proceed: {
// text: constants.DOWNLOAD,
// action: downloadApp,
// variant: 'success',
// },
// close: {
// text: constants.CLOSE,
// },
// });
// }
// }
// const router = useRouter();
// function onManageClick() {
// setIsOpen(false);
// galleryContext.showPlanSelectorModal();
// }
// const Divider = () => (
// <div
// style={{
// height: '1px',
// marginTop: '40px',
// background: '#242424',
// width: '100%',
// }}
// />
// );
// return (
// <Menu
// isOpen={isOpen}
// onStateChange={(state) => setIsOpen(state.isOpen)}
// itemListElement="div">
// <div
// style={{
// display: 'flex',
// outline: 'none',
// textAlign: 'center',
// }}>
// <LogoImage
// style={{ height: '24px', padding: '3px' }}
// alt="logo"
// src="/icon.svg"
// />
// </div>
// <div
// style={{
// outline: 'none',
// color: 'rgb(45, 194, 98)',
// fontSize: '16px',
// }}>
// {user?.email}
// </div>
// <div
// style={{
// flex: 1,
// overflow: 'auto',
// outline: 'none',
// paddingTop: '0',
// }}>
// <div style={{ outline: 'none' }}>
// <div style={{ display: 'flex' }}>
// <h5 style={{ margin: '4px 0 12px 2px' }}>
// {constants.SUBSCRIPTION_PLAN}
// </h5>
// </div>
// <div style={{ color: '#959595' }}>
// {isSubscriptionActive(subscription) ? (
// isOnFreePlan(subscription) ? (
// constants.FREE_SUBSCRIPTION_INFO(
// subscription?.expiryTime
// )
// ) : isSubscriptionCancelled(subscription) ? (
// constants.RENEWAL_CANCELLED_SUBSCRIPTION_INFO(
// subscription?.expiryTime
// )
// ) : (
// constants.RENEWAL_ACTIVE_SUBSCRIPTION_INFO(
// subscription?.expiryTime
// )
// )
// ) : (
// <p>{constants.SUBSCRIPTION_EXPIRED}</p>
// )}
// <Button
// variant="outline-success"
// block
// size="sm"
// onClick={onManageClick}>
// {isSubscribed(subscription)
// ? constants.MANAGE
// : constants.SUBSCRIBE}
// </Button>
// </div>
// </div>
// <div style={{ outline: 'none', marginTop: '30px' }} />
// <div>
// <h5 style={{ marginBottom: '12px' }}>
// {constants.USAGE_DETAILS}
// </h5>
// <div style={{ color: '#959595' }}>
// {usage ? (
// constants.USAGE_INFO(
// usage,
// convertToHumanReadable(subscription?.storage)
// )
// ) : (
// <div style={{ textAlign: 'center' }}>
// <EnteSpinner
// style={{
// borderWidth: '2px',
// width: '20px',
// height: '20px',
// }}
// />
// </div>
// )}
// </div>
// </div>
// <Divider />
// <LinkButton
// style={{ marginTop: '30px' }}
// onClick={() => {
// galleryContext.setActiveCollection(ARCHIVE_SECTION);
// setIsOpen(false);
// }}>
// {constants.ARCHIVE}
// </LinkButton>
// <LinkButton
// style={{ marginTop: '30px' }}
// onClick={() => {
// galleryContext.setActiveCollection(TRASH_SECTION);
// setIsOpen(false);
// }}>
// {constants.TRASH}
// </LinkButton>
// <>
// <RecoveryKeyModal
// show={recoverModalView}
// onHide={() => setRecoveryModalView(false)}
// somethingWentWrong={() =>
// props.setDialogMessage({
// title: constants.ERROR,
// content:
// constants.RECOVER_KEY_GENERATION_FAILED,
// close: { variant: 'danger' },
// })
// }
// />
// <LinkButton
// style={{ marginTop: '30px' }}
// onClick={() => setRecoveryModalView(true)}>
// {constants.DOWNLOAD_RECOVERY_KEY}
// </LinkButton>
// </>
// <>
// <TwoFactorModal
// show={twoFactorModalView}
// onHide={() => setTwoFactorModalView(false)}
// setDialogMessage={props.setDialogMessage}
// closeSidebar={() => setIsOpen(false)}
// setLoading={props.setLoading}
// />
// <LinkButton
// style={{ marginTop: '30px' }}
// onClick={() => setTwoFactorModalView(true)}>
// {constants.TWO_FACTOR}
// </LinkButton>
// </>
// <LinkButton
// style={{ marginTop: '30px' }}
// onClick={() => {
// router.push(PAGES.CHANGE_PASSWORD);
// }}>
// {constants.CHANGE_PASSWORD}
// </LinkButton>
// <LinkButton
// style={{ marginTop: '30px' }}
// onClick={() => {
// router.push(PAGES.CHANGE_EMAIL);
// }}>
// {constants.UPDATE_EMAIL}
// </LinkButton>
// <Divider />
// <>
// <FixLargeThumbnails
// isOpen={fixLargeThumbsView}
// hide={() => setFixLargeThumbsView(false)}
// show={() => setFixLargeThumbsView(true)}
// />
// <LinkButton
// style={{ marginTop: '30px' }}
// onClick={() => setFixLargeThumbsView(true)}>
// {constants.FIX_LARGE_THUMBNAILS}
// </LinkButton>
// </>
// <LinkButton
// style={{ marginTop: '30px' }}
// onClick={openFeedbackURL}>
// {constants.REQUEST_FEATURE}
// </LinkButton>
// <LinkButton
// style={{ marginTop: '30px' }}
// onClick={() => {
// if (!appContext.mlSearchEnabled) {
// if (!canEnableMlSearch()) {
// props.setDialogMessage({
// title: constants.ENABLE_ML_SEARCH,
// content: constants.ML_SEARCH_NOT_COMPATIBLE,
// close: { text: constants.OK },
// });
// return;
// }
// props.setDialogMessage({
// title: `${constants.CONFIRM} ${constants.ENABLE_ML_SEARCH}`,
// content: constants.ENABLE_ML_SEARCH_MESSAGE,
// staticBackdrop: true,
// proceed: {
// text: constants.ENABLE_ML_SEARCH,
// action: enableMlSearch,
// variant: 'success',
// },
// close: { text: constants.CANCEL },
// });
// } else {
// disableMlSearch();
// }
// }}>
// {appContext.mlSearchEnabled
// ? constants.DISABLE_ML_SEARCH
// : constants.ENABLE_ML_SEARCH}
// </LinkButton>
// <LinkButton
// style={{ marginTop: '30px' }}
// onClick={() => {
// if (!appContext.mlSearchEnabled) {
// if (!canEnableMlSearch()) {
// props.setDialogMessage({
// title: constants.ENABLE_ML_SEARCH,
// content: constants.ML_SEARCH_NOT_COMPATIBLE,
// close: { text: constants.OK },
// });
// return;
// }
// props.setDialogMessage({
// title: 'clear mb db',
// content: 'clear mb db',
// staticBackdrop: true,
// proceed: {
// text: 'clear',
// action: clearMLDB,
// variant: 'success',
// },
// close: { text: constants.CANCEL },
// });
// } else {
// disableMlSearch();
// }
// }}>
// {'clear ML db'}
// </LinkButton>
// {appContext.mlSearchEnabled && (
// <LinkButton
// style={{ marginTop: '30px' }}
// onClick={() => {
// router.push(PAGES.ML_DEBUG);
// }}>
// {constants.ML_DEBUG}
// </LinkButton>
// )}
// <LinkButton
// style={{ marginTop: '30px' }}
// onClick={() => initiateEmail('contact@ente.io')}>
// {constants.SUPPORT}
// </LinkButton>
// <>
// <ExportModal
// show={exportModalView}
// onHide={() => setExportModalView(false)}
// usage={usage}
// />
// <LinkButton
// style={{ marginTop: '30px' }}
// onClick={exportFiles}>
// <div style={{ display: 'flex' }}>
// {constants.EXPORT}
// <div style={{ width: '20px' }} />
// {exportService.isExportInProgress() && (
// <InProgressIcon />
// )}
// </div>
// </LinkButton>
// </>
// <Divider />
// <LinkButton
// variant="danger"
// style={{ marginTop: '30px' }}
// onClick={() =>
// props.setDialogMessage({
// title: `${constants.CONFIRM} ${constants.LOGOUT}`,
// content: constants.LOGOUT_MESSAGE,
// staticBackdrop: true,
// proceed: {
// text: constants.LOGOUT,
// action: logoutUser,
// variant: 'danger',
// },
// close: { text: constants.CANCEL },
// })
// }>
// {constants.LOGOUT}
// </LinkButton>
// <LinkButton
// variant="danger"
// style={{ marginTop: '30px' }}
// onClick={() =>
// props.setDialogMessage({
// title: `${constants.DELETE_ACCOUNT}`,
// content: constants.DELETE_ACCOUNT_MESSAGE(),
// staticBackdrop: true,
// proceed: {
// text: constants.DELETE_ACCOUNT,
// action: () => {
// initiateEmail('account-deletion@ente.io');
// },
// variant: 'danger',
// },
// close: { text: constants.CANCEL },
// })
// }>
// {constants.DELETE_ACCOUNT}
// </LinkButton>
// <div
// style={{
// marginTop: '40px',
// width: '100%',
// }}
// />
// </div>
// </Menu>
// );
// }

View file

@ -1,454 +0,0 @@
import React, { useContext, useEffect, useState } from 'react';
import { slide as Menu } from 'react-burger-menu';
import constants from 'utils/strings/constants';
import { getData, LS_KEYS, setData } from 'utils/storage/localStorage';
import { getToken } from 'utils/common/key';
import { getEndpoint } from 'utils/common/apiUtil';
import { Button } from 'react-bootstrap';
import {
isSubscriptionActive,
getUserSubscription,
isOnFreePlan,
isSubscriptionCancelled,
isSubscribed,
convertToHumanReadable,
} from 'utils/billing';
import isElectron from 'is-electron';
import { Collection } from 'types/collection';
import { useRouter } from 'next/router';
import LinkButton from './pages/gallery/LinkButton';
import { downloadApp } from 'utils/common';
import { getUserDetails, logoutUser } from 'services/userService';
import { LogoImage } from 'pages/_app';
import { SetDialogMessage } from './MessageDialog';
import EnteSpinner from './EnteSpinner';
import RecoveryKeyModal from './RecoveryKeyModal';
import TwoFactorModal from './TwoFactorModal';
import ExportModal from './ExportModal';
import { GalleryContext } from 'pages/gallery';
import InProgressIcon from './icons/InProgressIcon';
import exportService from 'services/exportService';
import { Subscription } from 'types/billing';
import { PAGES } from 'constants/pages';
import { ARCHIVE_SECTION, TRASH_SECTION } from 'constants/collection';
import FixLargeThumbnails from './FixLargeThumbnail';
import { AppContext } from 'pages/_app';
import { canEnableMlSearch } from 'utils/machineLearning/compatibility';
import { SetLoading } from 'types/gallery';
import mlIDbStorage from 'utils/storage/mlIDbStorage';
interface Props {
collections: Collection[];
setDialogMessage: SetDialogMessage;
setLoading: SetLoading;
}
export default function Sidebar(props: Props) {
const [usage, SetUsage] = useState<string>(null);
const [user, setUser] = useState(null);
const [subscription, setSubscription] = useState<Subscription>(null);
useEffect(() => {
setUser(getData(LS_KEYS.USER));
setSubscription(getUserSubscription());
}, []);
const [isOpen, setIsOpen] = useState(false);
const [recoverModalView, setRecoveryModalView] = useState(false);
const [twoFactorModalView, setTwoFactorModalView] = useState(false);
const [exportModalView, setExportModalView] = useState(false);
const [fixLargeThumbsView, setFixLargeThumbsView] = useState(false);
const galleryContext = useContext(GalleryContext);
const appContext = useContext(AppContext);
const enableMlSearch = async () => {
await appContext.updateMlSearchEnabled(true);
};
const disableMlSearch = async () => {
await appContext.updateMlSearchEnabled(false);
};
const clearMLDB = async () => {
await mlIDbStorage.clearMLDB();
};
useEffect(() => {
const main = async () => {
if (!isOpen) {
return;
}
const userDetails = await getUserDetails();
setUser({ ...user, email: userDetails.email });
SetUsage(convertToHumanReadable(userDetails.usage));
setSubscription(userDetails.subscription);
setData(LS_KEYS.USER, {
...getData(LS_KEYS.USER),
email: userDetails.email,
});
setData(LS_KEYS.SUBSCRIPTION, userDetails.subscription);
};
main();
}, [isOpen]);
function openFeedbackURL() {
const feedbackURL: string = `${getEndpoint()}/users/feedback?token=${encodeURIComponent(
getToken()
)}`;
const win = window.open(feedbackURL, '_blank');
win.focus();
}
function initiateEmail(email: string) {
const a = document.createElement('a');
a.href = 'mailto:' + email;
a.rel = 'noreferrer noopener';
a.click();
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function exportFiles() {
if (isElectron()) {
setExportModalView(true);
} else {
props.setDialogMessage({
title: constants.DOWNLOAD_APP,
content: constants.DOWNLOAD_APP_MESSAGE(),
staticBackdrop: true,
proceed: {
text: constants.DOWNLOAD,
action: downloadApp,
variant: 'success',
},
close: {
text: constants.CLOSE,
},
});
}
}
const router = useRouter();
function onManageClick() {
setIsOpen(false);
galleryContext.showPlanSelectorModal();
}
const Divider = () => (
<div
style={{
height: '1px',
marginTop: '40px',
background: '#242424',
width: '100%',
}}
/>
);
return (
<Menu
isOpen={isOpen}
onStateChange={(state) => setIsOpen(state.isOpen)}
itemListElement="div">
<div
style={{
display: 'flex',
outline: 'none',
textAlign: 'center',
}}>
<LogoImage
style={{ height: '24px', padding: '3px' }}
alt="logo"
src="/icon.svg"
/>
</div>
<div
style={{
outline: 'none',
color: 'rgb(45, 194, 98)',
fontSize: '16px',
}}>
{user?.email}
</div>
<div
style={{
flex: 1,
overflow: 'auto',
outline: 'none',
paddingTop: '0',
}}>
<div style={{ outline: 'none' }}>
<div style={{ display: 'flex' }}>
<h5 style={{ margin: '4px 0 12px 2px' }}>
{constants.SUBSCRIPTION_PLAN}
</h5>
</div>
<div style={{ color: '#959595' }}>
{isSubscriptionActive(subscription) ? (
isOnFreePlan(subscription) ? (
constants.FREE_SUBSCRIPTION_INFO(
subscription?.expiryTime
)
) : isSubscriptionCancelled(subscription) ? (
constants.RENEWAL_CANCELLED_SUBSCRIPTION_INFO(
subscription?.expiryTime
)
) : (
constants.RENEWAL_ACTIVE_SUBSCRIPTION_INFO(
subscription?.expiryTime
)
)
) : (
<p>{constants.SUBSCRIPTION_EXPIRED}</p>
)}
<Button
variant="outline-success"
block
size="sm"
onClick={onManageClick}>
{isSubscribed(subscription)
? constants.MANAGE
: constants.SUBSCRIBE}
</Button>
</div>
</div>
<div style={{ outline: 'none', marginTop: '30px' }} />
<div>
<h5 style={{ marginBottom: '12px' }}>
{constants.USAGE_DETAILS}
</h5>
<div style={{ color: '#959595' }}>
{usage ? (
constants.USAGE_INFO(
usage,
convertToHumanReadable(subscription?.storage)
)
) : (
<div style={{ textAlign: 'center' }}>
<EnteSpinner
style={{
borderWidth: '2px',
width: '20px',
height: '20px',
}}
/>
</div>
)}
</div>
</div>
<Divider />
<LinkButton
style={{ marginTop: '30px' }}
onClick={() => {
galleryContext.setActiveCollection(ARCHIVE_SECTION);
setIsOpen(false);
}}>
{constants.ARCHIVE}
</LinkButton>
<LinkButton
style={{ marginTop: '30px' }}
onClick={() => {
galleryContext.setActiveCollection(TRASH_SECTION);
setIsOpen(false);
}}>
{constants.TRASH}
</LinkButton>
<>
<RecoveryKeyModal
show={recoverModalView}
onHide={() => setRecoveryModalView(false)}
somethingWentWrong={() =>
props.setDialogMessage({
title: constants.ERROR,
content:
constants.RECOVER_KEY_GENERATION_FAILED,
close: { variant: 'danger' },
})
}
/>
<LinkButton
style={{ marginTop: '30px' }}
onClick={() => setRecoveryModalView(true)}>
{constants.DOWNLOAD_RECOVERY_KEY}
</LinkButton>
</>
<>
<TwoFactorModal
show={twoFactorModalView}
onHide={() => setTwoFactorModalView(false)}
setDialogMessage={props.setDialogMessage}
closeSidebar={() => setIsOpen(false)}
setLoading={props.setLoading}
/>
<LinkButton
style={{ marginTop: '30px' }}
onClick={() => setTwoFactorModalView(true)}>
{constants.TWO_FACTOR}
</LinkButton>
</>
<LinkButton
style={{ marginTop: '30px' }}
onClick={() => {
router.push(PAGES.CHANGE_PASSWORD);
}}>
{constants.CHANGE_PASSWORD}
</LinkButton>
<LinkButton
style={{ marginTop: '30px' }}
onClick={() => {
router.push(PAGES.CHANGE_EMAIL);
}}>
{constants.UPDATE_EMAIL}
</LinkButton>
<Divider />
<>
<FixLargeThumbnails
isOpen={fixLargeThumbsView}
hide={() => setFixLargeThumbsView(false)}
show={() => setFixLargeThumbsView(true)}
/>
<LinkButton
style={{ marginTop: '30px' }}
onClick={() => setFixLargeThumbsView(true)}>
{constants.FIX_LARGE_THUMBNAILS}
</LinkButton>
</>
<LinkButton
style={{ marginTop: '30px' }}
onClick={openFeedbackURL}>
{constants.REQUEST_FEATURE}
</LinkButton>
<LinkButton
style={{ marginTop: '30px' }}
onClick={() => {
if (!appContext.mlSearchEnabled) {
if (!canEnableMlSearch()) {
props.setDialogMessage({
title: constants.ENABLE_ML_SEARCH,
content: constants.ML_SEARCH_NOT_COMPATIBLE,
close: { text: constants.OK },
});
return;
}
props.setDialogMessage({
title: `${constants.CONFIRM} ${constants.ENABLE_ML_SEARCH}`,
content: constants.ENABLE_ML_SEARCH_MESSAGE,
staticBackdrop: true,
proceed: {
text: constants.ENABLE_ML_SEARCH,
action: enableMlSearch,
variant: 'success',
},
close: { text: constants.CANCEL },
});
} else {
disableMlSearch();
}
}}>
{appContext.mlSearchEnabled
? constants.DISABLE_ML_SEARCH
: constants.ENABLE_ML_SEARCH}
</LinkButton>
<LinkButton
style={{ marginTop: '30px' }}
onClick={() => {
if (!appContext.mlSearchEnabled) {
if (!canEnableMlSearch()) {
props.setDialogMessage({
title: constants.ENABLE_ML_SEARCH,
content: constants.ML_SEARCH_NOT_COMPATIBLE,
close: { text: constants.OK },
});
return;
}
props.setDialogMessage({
title: 'clear mb db',
content: 'clear mb db',
staticBackdrop: true,
proceed: {
text: 'clear',
action: clearMLDB,
variant: 'success',
},
close: { text: constants.CANCEL },
});
} else {
disableMlSearch();
}
}}>
{'clear ML db'}
</LinkButton>
{appContext.mlSearchEnabled && (
<LinkButton
style={{ marginTop: '30px' }}
onClick={() => {
router.push(PAGES.ML_DEBUG);
}}>
{constants.ML_DEBUG}
</LinkButton>
)}
<LinkButton
style={{ marginTop: '30px' }}
onClick={() => initiateEmail('contact@ente.io')}>
{constants.SUPPORT}
</LinkButton>
<>
<ExportModal
show={exportModalView}
onHide={() => setExportModalView(false)}
usage={usage}
/>
<LinkButton
style={{ marginTop: '30px' }}
onClick={exportFiles}>
<div style={{ display: 'flex' }}>
{constants.EXPORT}
<div style={{ width: '20px' }} />
{exportService.isExportInProgress() && (
<InProgressIcon />
)}
</div>
</LinkButton>
</>
<Divider />
<LinkButton
variant="danger"
style={{ marginTop: '30px' }}
onClick={() =>
props.setDialogMessage({
title: `${constants.CONFIRM} ${constants.LOGOUT}`,
content: constants.LOGOUT_MESSAGE,
staticBackdrop: true,
proceed: {
text: constants.LOGOUT,
action: logoutUser,
variant: 'danger',
},
close: { text: constants.CANCEL },
})
}>
{constants.LOGOUT}
</LinkButton>
<LinkButton
variant="danger"
style={{ marginTop: '30px' }}
onClick={() =>
props.setDialogMessage({
title: `${constants.DELETE_ACCOUNT}`,
content: constants.DELETE_ACCOUNT_MESSAGE(),
staticBackdrop: true,
proceed: {
text: constants.DELETE_ACCOUNT,
action: () => {
initiateEmail('account-deletion@ente.io');
},
variant: 'danger',
},
close: { text: constants.CANCEL },
})
}>
{constants.DELETE_ACCOUNT}
</LinkButton>
<div
style={{
marginTop: '40px',
width: '100%',
}}
/>
</div>
</Menu>
);
}

View file

@ -1,73 +1,75 @@
import CollectionShare from 'components/CollectionShare';
import { SetDialogMessage } from 'components/MessageDialog';
import NavigationButton, {
SCROLL_DIRECTION,
} from 'components/NavigationButton';
import React, { useEffect, useRef, useState } from 'react';
import { OverlayTrigger, Tooltip } from 'react-bootstrap';
import { sortCollections } from 'services/collectionService';
import { User } from 'types/user';
// import CollectionShare from 'components/Collections/CollectionShare';
// import { SetDialogMessage } from 'components/MessageDialog';
// import NavigationButton, {
// SCROLL_DIRECTION,
// } from 'components/NavigationButton';
// import React, { useEffect, useRef, useState } from 'react';
// import { OverlayTrigger, Tooltip } from 'react-bootstrap';
// import { sortCollections } from 'services/collectionService';
// import { User } from 'types/user';
import styled from 'styled-components';
import { IMAGE_CONTAINER_MAX_WIDTH } from 'constants/gallery';
import { Collection, CollectionAndItsLatestFile } from 'types/collection';
import { getSelectedCollection } from 'utils/collection';
import { getData, LS_KEYS } from 'utils/storage/localStorage';
import constants from 'utils/strings/constants';
import { SetCollectionNamerAttributes } from './CollectionNamer';
import CollectionOptions from './CollectionOptions';
import CollectionSort from './CollectionSort';
import OptionIcon, { OptionIconWrapper } from './OptionIcon';
import {
ALL_SECTION,
ARCHIVE_SECTION,
CollectionType,
COLLECTION_SORT_BY,
TRASH_SECTION,
} from 'constants/collection';
// import { IMAGE_CONTAINER_MAX_WIDTH } from 'constants/gallery';
// import { Collection, CollectionAndItsLatestFile } from 'types/collection';
// import { getSelectedCollection } from 'utils/collection';
// import { getData, LS_KEYS } from 'utils/storage/localStorage';
// import constants from 'utils/strings/constants';
// import { SetCollectionNamerAttributes } from './CollectionNamer';
// import CollectionOptions from './CollectionOptions';
// import CollectionSort from './CollectionSort';
// import OptionIcon, { OptionIconWrapper } from './OptionIcon';
// import {
// ALL_SECTION,
// ARCHIVE_SECTION,
// CollectionType,
// COLLECTION_SORT_BY,
// TRASH_SECTION,
// } from 'constants/collection';
interface CollectionProps {
collections: Collection[];
collectionAndTheirLatestFile: CollectionAndItsLatestFile[];
activeCollection?: number;
setActiveCollection: (id?: number) => void;
setDialogMessage: SetDialogMessage;
syncWithRemote: () => Promise<void>;
setCollectionNamerAttributes: SetCollectionNamerAttributes;
startLoading: () => void;
finishLoading: () => void;
isInSearchMode: boolean;
collectionFilesCount: Map<number, number>;
}
import { OptionIconWrapper } from './OptionIcon';
const CollectionContainer = styled.div`
overflow-y: hidden;
height: 40px;
display: flex;
width: calc(100% - 80px);
position: relative;
padding: 0 24px;
// interface CollectionProps {
// collections: Collection[];
// collectionAndTheirLatestFile: CollectionAndItsLatestFile[];
// activeCollection?: number;
// setActiveCollection: (id?: number) => void;
// setDialogMessage: SetDialogMessage;
// syncWithRemote: () => Promise<void>;
// setCollectionNamerAttributes: SetCollectionNamerAttributes;
// startLoading: () => void;
// finishLoading: () => void;
// isInSearchMode: boolean;
// collectionFilesCount: Map<number, number>;
// }
@media (max-width: ${IMAGE_CONTAINER_MAX_WIDTH * 4}px) {
padding: 0 4px;
}
`;
// const CollectionContainer = styled.div`
// overflow-y: hidden;
// height: 40px;
// display: flex;
// width: calc(100% - 80px);
// position: relative;
// padding: 0 24px;
const Wrapper = styled.div`
height: 70px;
flex: 1;
white-space: nowrap;
overflow: auto;
max-width: 100%;
scroll-behavior: smooth;
`;
// @media (max-width: ${IMAGE_CONTAINER_MAX_WIDTH * 4}px) {
// padding: 0 4px;
// }
// `;
const CollectionBar = styled.div`
width: 100%;
margin: 10px auto;
display: flex;
align-items: center;
justify-content: flex-start;
`;
// const Wrapper = styled.div`
// height: 70px;
// flex: 1;
// white-space: nowrap;
// overflow: auto;
// max-width: 100%;
// scroll-behavior: smooth;
// `;
// const CollectionBar = styled.div`
// width: 100%;
// margin: 10px auto;
// display: flex;
// align-items: center;
// justify-content: flex-start;
// `;
export const Chip = styled.button<{ active: boolean }>`
border-radius: 8px;
@ -87,196 +89,196 @@ export const Chip = styled.button<{ active: boolean }>`
}
`;
const SectionChipCreater =
({ activeCollection, clickHandler }) =>
({ section, label }) =>
(
<Chip
active={activeCollection === section}
onClick={clickHandler(section)}>
{label}
<div
style={{
display: 'inline-block',
width: '24px',
}}
/>
</Chip>
);
const Hider = styled.div<{ hide: boolean }>`
opacity: ${(props) => (props.hide ? '0' : '100')};
height: ${(props) => (props.hide ? '0' : 'auto')};
`;
// const SectionChipCreater =
// ({ activeCollection, clickHandler }) =>
// ({ section, label }) =>
// (
// <Chip
// active={activeCollection === section}
// onClick={clickHandler(section)}>
// {label}
// <div
// style={{
// display: 'inline-block',
// width: '24px',
// }}
// />
// </Chip>
// );
// const Hider = styled.div<{ hide: boolean }>`
// opacity: ${(props) => (props.hide ? '0' : '100')};
// height: ${(props) => (props.hide ? '0' : 'auto')};
// `;
export default function Collections(props: CollectionProps) {
const { activeCollection, collections, setActiveCollection } = props;
const [selectedCollectionID, setSelectedCollectionID] =
useState<number>(null);
const collectionWrapperRef = useRef<HTMLDivElement>(null);
const collectionChipsRef = props.collections.reduce(
(refMap, collection) => {
refMap[collection.id] = React.createRef();
return refMap;
},
{}
);
const [collectionShareModalView, setCollectionShareModalView] =
useState(false);
const [scrollObj, setScrollObj] = useState<{
scrollLeft?: number;
scrollWidth?: number;
clientWidth?: number;
}>({});
const [collectionSortBy, setCollectionSortBy] =
useState<COLLECTION_SORT_BY>(COLLECTION_SORT_BY.LATEST_FILE);
// export default function Collections(props: CollectionProps) {
// const { activeCollection, collections, setActiveCollection } = props;
// const [selectedCollectionID, setSelectedCollectionID] =
// useState<number>(null);
// const collectionWrapperRef = useRef<HTMLDivElement>(null);
// const collectionChipsRef = props.collections.reduce(
// (refMap, collection) => {
// refMap[collection.id] = React.createRef();
// return refMap;
// },
// {}
// );
// const [collectionShareModalView, setCollectionShareModalView] =
// useState(false);
// const [scrollObj, setScrollObj] = useState<{
// scrollLeft?: number;
// scrollWidth?: number;
// clientWidth?: number;
// }>({});
// const [collectionSortBy, setCollectionSortBy] =
// useState<COLLECTION_SORT_BY>(COLLECTION_SORT_BY.LATEST_FILE);
const updateScrollObj = () => {
if (collectionWrapperRef.current) {
const { scrollLeft, scrollWidth, clientWidth } =
collectionWrapperRef.current;
setScrollObj({ scrollLeft, scrollWidth, clientWidth });
}
};
// const updateScrollObj = () => {
// if (collectionWrapperRef.current) {
// const { scrollLeft, scrollWidth, clientWidth } =
// collectionWrapperRef.current;
// setScrollObj({ scrollLeft, scrollWidth, clientWidth });
// }
// };
useEffect(() => {
updateScrollObj();
}, [collectionWrapperRef.current, props.isInSearchMode, collections]);
// useEffect(() => {
// updateScrollObj();
// }, [collectionWrapperRef.current, props.isInSearchMode, collections]);
useEffect(() => {
if (!collectionWrapperRef?.current) {
return;
}
collectionWrapperRef.current.scrollLeft = 0;
}, [collections]);
// useEffect(() => {
// if (!collectionWrapperRef?.current) {
// return;
// }
// collectionWrapperRef.current.scrollLeft = 0;
// }, [collections]);
useEffect(() => {
collectionChipsRef[activeCollection]?.current.scrollIntoView({
inline: 'center',
});
}, [activeCollection]);
// useEffect(() => {
// collectionChipsRef[activeCollection]?.current.scrollIntoView({
// inline: 'center',
// });
// }, [activeCollection]);
const clickHandler = (collectionID?: number) => () => {
setSelectedCollectionID(collectionID);
setActiveCollection(collectionID ?? ALL_SECTION);
};
// const clickHandler = (collectionID?: number) => () => {
// setSelectedCollectionID(collectionID);
// setActiveCollection(collectionID ?? ALL_SECTION);
// };
const user: User = getData(LS_KEYS.USER);
// const user: User = getData(LS_KEYS.USER);
const collectionOptions = CollectionOptions({
syncWithRemote: props.syncWithRemote,
setCollectionNamerAttributes: props.setCollectionNamerAttributes,
collections: props.collections,
selectedCollectionID,
setDialogMessage: props.setDialogMessage,
startLoading: props.startLoading,
finishLoading: props.finishLoading,
showCollectionShareModal: setCollectionShareModalView.bind(null, true),
redirectToAll: setActiveCollection.bind(null, ALL_SECTION),
});
// const collectionOptions = CollectionOptions({
// syncWithRemote: props.syncWithRemote,
// setCollectionNamerAttributes: props.setCollectionNamerAttributes,
// collections: props.collections,
// selectedCollectionID,
// setDialogMessage: props.setDialogMessage,
// startLoading: props.startLoading,
// finishLoading: props.finishLoading,
// showCollectionShareModal: setCollectionShareModalView.bind(null, true),
// redirectToAll: setActiveCollection.bind(null, ALL_SECTION),
// });
const scrollCollection = (direction: SCROLL_DIRECTION) => () => {
collectionWrapperRef.current.scrollBy(250 * direction, 0);
};
const renderTooltip = (collectionID: number) => {
const fileCount = props.collectionFilesCount?.get(collectionID) ?? 0;
return (
<Tooltip id="button-tooltip">
{fileCount} {fileCount > 1 ? 'items' : 'item'}
</Tooltip>
);
};
// const scrollCollection = (direction: SCROLL_DIRECTION) => () => {
// collectionWrapperRef.current.scrollBy(250 * direction, 0);
// };
// const renderTooltip = (collectionID: number) => {
// const fileCount = props.collectionFilesCount?.get(collectionID) ?? 0;
// return (
// <Tooltip id="button-tooltip">
// {fileCount} {fileCount > 1 ? 'items' : 'item'}
// </Tooltip>
// );
// };
const SectionChip = SectionChipCreater({ activeCollection, clickHandler });
// const SectionChip = SectionChipCreater({ activeCollection, clickHandler });
return (
<Hider hide={props.isInSearchMode}>
<CollectionShare
show={collectionShareModalView}
onHide={() => setCollectionShareModalView(false)}
collection={getSelectedCollection(
selectedCollectionID,
props.collections
)}
syncWithRemote={props.syncWithRemote}
/>
<CollectionBar>
<CollectionContainer>
{scrollObj.scrollLeft > 0 && (
<NavigationButton
scrollDirection={SCROLL_DIRECTION.LEFT}
onClick={scrollCollection(SCROLL_DIRECTION.LEFT)}
/>
)}
<Wrapper
ref={collectionWrapperRef}
onScroll={updateScrollObj}>
<SectionChip
section={ALL_SECTION}
label={constants.ALL}
/>
{sortCollections(
collections,
props.collectionAndTheirLatestFile,
collectionSortBy
).map((item) => (
<OverlayTrigger
key={item.id}
placement="top"
delay={{ show: 250, hide: 400 }}
overlay={renderTooltip(item.id)}>
<Chip
ref={collectionChipsRef[item.id]}
active={activeCollection === item.id}
onClick={clickHandler(item.id)}>
{item.name}
{item.type !== CollectionType.favorites &&
item.owner.id === user?.id ? (
<OverlayTrigger
rootClose
trigger="click"
placement="bottom"
overlay={collectionOptions}>
<OptionIcon
onClick={() =>
setSelectedCollectionID(
item.id
)
}
/>
</OverlayTrigger>
) : (
<div
style={{
display: 'inline-block',
width: '24px',
}}
/>
)}
</Chip>
</OverlayTrigger>
))}
<SectionChip
section={ARCHIVE_SECTION}
label={constants.ARCHIVE}
/>
<SectionChip
section={TRASH_SECTION}
label={constants.TRASH}
/>
</Wrapper>
{scrollObj.scrollLeft <
scrollObj.scrollWidth - scrollObj.clientWidth && (
<NavigationButton
scrollDirection={SCROLL_DIRECTION.RIGHT}
onClick={scrollCollection(SCROLL_DIRECTION.RIGHT)}
/>
)}
</CollectionContainer>
<CollectionSort
setCollectionSortBy={setCollectionSortBy}
activeSortBy={collectionSortBy}
/>
</CollectionBar>
</Hider>
);
}
// return (
// <Hider hide={props.isInSearchMode}>
// <CollectionShare
// show={collectionShareModalView}
// onHide={() => setCollectionShareModalView(false)}
// collection={getSelectedCollection(
// selectedCollectionID,
// props.collections
// )}
// syncWithRemote={props.syncWithRemote}
// />
// <CollectionBar>
// <CollectionContainer>
// {scrollObj.scrollLeft > 0 && (
// <NavigationButton
// scrollDirection={SCROLL_DIRECTION.LEFT}
// onClick={scrollCollection(SCROLL_DIRECTION.LEFT)}
// />
// )}
// <Wrapper
// ref={collectionWrapperRef}
// onScroll={updateScrollObj}>
// <SectionChip
// section={ALL_SECTION}
// label={constants.ALL}
// />
// {sortCollections(
// collections,
// props.collectionAndTheirLatestFile,
// collectionSortBy
// ).map((item) => (
// <OverlayTrigger
// key={item.id}
// placement="top"
// delay={{ show: 250, hide: 400 }}
// overlay={renderTooltip(item.id)}>
// <Chip
// ref={collectionChipsRef[item.id]}
// active={activeCollection === item.id}
// onClick={clickHandler(item.id)}>
// {item.name}
// {item.type !== CollectionType.favorites &&
// item.owner.id === user?.id ? (
// <OverlayTrigger
// rootClose
// trigger="click"
// placement="bottom"
// overlay={collectionOptions}>
// <OptionIcon
// onClick={() =>
// setSelectedCollectionID(
// item.id
// )
// }
// />
// </OverlayTrigger>
// ) : (
// <div
// style={{
// display: 'inline-block',
// width: '24px',
// }}
// />
// )}
// </Chip>
// </OverlayTrigger>
// ))}
// <SectionChip
// section={ARCHIVE_SECTION}
// label={constants.ARCHIVE}
// />
// <SectionChip
// section={TRASH_SECTION}
// label={constants.TRASH}
// />
// </Wrapper>
// {scrollObj.scrollLeft <
// scrollObj.scrollWidth - scrollObj.clientWidth && (
// <NavigationButton
// scrollDirection={SCROLL_DIRECTION.RIGHT}
// onClick={scrollCollection(SCROLL_DIRECTION.RIGHT)}
// />
// )}
// </CollectionContainer>
// <CollectionSort
// setCollectionSortBy={setCollectionSortBy}
// activeSortBy={collectionSortBy}
// />
// </CollectionBar>
// </Hider>
// );
// }

View file

@ -23,7 +23,8 @@ import {
SceneDetectionMethod,
} from 'types/machineLearning';
import { CONCURRENCY } from 'utils/common/concurrency';
import { ComlinkWorker, getDedicatedCryptoWorker } from 'utils/crypto';
import { getDedicatedCryptoWorker } from 'utils/crypto';
import { ComlinkWorker } from 'utils/comlink';
import { logQueueStats } from 'utils/machineLearning';
import arcfaceAlignmentService from './arcfaceAlignmentService';
import arcfaceCropService from './arcfaceCropService';

View file

@ -21,8 +21,7 @@ class ReaderService {
}
fileContext.imageBitmap = await getLocalFileImageBitmap(
fileContext.enteFile,
fileContext.localFile,
() => syncContext.getEnteWorker(fileContext.enteFile.id)
fileContext.localFile
);
} else if (
syncContext.config.imageSource === 'Original' &&

View file

@ -8,7 +8,7 @@ import Tesseract, { createWorker } from 'tesseract.js';
import QueueProcessor from 'services/queueProcessor';
import { CustomError } from 'utils/error';
import { imageBitmapToBlob, resizeToSquare } from 'utils/image';
import { getFileType } from 'services/upload/readFileService';
import { getFileType } from 'services/typeDetectionService';
import { FILE_TYPE } from 'constants/file';
import { makeID } from 'utils/user';
import {
@ -129,7 +129,7 @@ class TesseractService implements TextDetectionService {
[await imageBitmapToBlob(imageBitmap)],
'text-detection-dummy-image'
);
const fileTypeInfo = await getFileType(new FileReader(), file);
const fileTypeInfo = await getFileType(file);
if (
fileTypeInfo.fileType !== FILE_TYPE.IMAGE &&

View file

@ -1,4 +1,4 @@
import { Person, Thing } from 'types/machineLearning';
import { Person, Thing, ThingClass, WordGroup } from 'types/machineLearning';
import { IndexStatus } from 'types/machineLearning/ui';
import { EnteFile } from 'types/file';
@ -39,6 +39,9 @@ export type Search = {
location?: Bbox;
collection?: number;
file?: number;
person?: Person;
thing?: ThingClass;
text?: WordGroup;
};
export type SearchResultSummary = {

View file

@ -355,7 +355,7 @@ async function getOriginalConvertedFile(
) {
let fileBlob = await getOriginalImageFile(file, token, enteWorker, queue);
if (needsConversionForPreview(file)) {
fileBlob = await convertForPreview(file, fileBlob, enteWorker);
fileBlob = await convertForPreview(file, fileBlob)[0];
}
return fileBlob;
@ -402,13 +402,11 @@ export async function getThumbnailImageBitmap(file: EnteFile, token: string) {
export async function getLocalFileImageBitmap(
enteFile: EnteFile,
localFile: globalThis.File,
enteWorkerProvider?: () => Promise<any>
localFile: globalThis.File
) {
let fileBlob = localFile as Blob;
if (needsConversionForPreview(enteFile)) {
const enteWorker = await enteWorkerProvider();
fileBlob = await convertForPreview(enteFile, fileBlob, enteWorker);
fileBlob = await convertForPreview(enteFile, fileBlob)[0];
}
return getImageBlobBitmap(fileBlob);
}

View file

@ -2294,6 +2294,16 @@ bluebird@^3.5.5:
resolved "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz"
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
blueimp-load-image@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/blueimp-load-image/-/blueimp-load-image-3.0.0.tgz#d71c39440a7d2f1a83e3e86a625e329116a51705"
integrity sha512-Q9rFbd4ZUNvzSFmRXx9MoG0RwWwJeMjjEUbG7WIOJgUg22Jgkow0wL5b35B6qwiBscxACW9OHdrP5s2vQ3x8DQ==
bmp-js@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/bmp-js/-/bmp-js-0.1.0.tgz#e05a63f796a6c1ff25f4771ec7adadc148c07233"
integrity sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==
body-parser@1.19.0:
version "1.19.0"
resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz"
@ -2532,6 +2542,11 @@ colorette@^1.2.2, colorette@^1.3.0:
resolved "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz"
integrity sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==
colors@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78"
integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==
comlink@^4.3.0:
version "4.3.1"
resolved "https://registry.npmjs.org/comlink/-/comlink-4.3.1.tgz"
@ -3495,6 +3510,11 @@ file-selector@^0.2.2:
dependencies:
tslib "^2.0.3"
file-type@^12.4.1:
version "12.4.2"
resolved "https://registry.yarnpkg.com/file-type/-/file-type-12.4.2.tgz#a344ea5664a1d01447ee7fb1b635f72feb6169d9"
integrity sha512-UssQP5ZgIOKelfsaB5CuGAL+Y+q7EmONuiwF3N5HAH0t27rvrttgi6Ra9k/+DVaY9UF6+ybxu5pOXLUdA8N7Vg==
file-type@^16.5.4:
version "16.5.4"
resolved "https://registry.yarnpkg.com/file-type/-/file-type-16.5.4.tgz#474fb4f704bee427681f98dd390058a172a6c2fd"
@ -3901,6 +3921,11 @@ iconv-lite@0.4.24:
dependencies:
safer-buffer ">= 2.1.2 < 3"
idb-keyval@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-3.2.0.tgz#cbbf354deb5684b6cdc84376294fc05932845bd6"
integrity sha512-slx8Q6oywCCSfKgPgL0sEsXtPVnSbTLWpyiDcu6msHOyKOLari1TD1qocXVCft80umnkk3/Qqh3lwoFt8T/BPQ==
idb@^6.0.0:
version "6.1.3"
resolved "https://registry.npmjs.org/idb/-/idb-6.1.3.tgz"
@ -4216,7 +4241,18 @@ jest-worker@^24.9.0:
merge-stream "^2.0.0"
supports-color "^6.1.0"
jpeg-js@^0.4.1:
jpeg-autorotate@^7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/jpeg-autorotate/-/jpeg-autorotate-7.1.1.tgz#c57905c6afd3b54373a6a1d0249ed6e07f7b043b"
integrity sha512-ewTZTG/QWOM0D5h/yKcQ3QgyrnQYsr3qmcS+bqoAwgQAY1KBa31aJ+q+FlElaxo/rSYqfF1ixf+8EIgluBkgTg==
dependencies:
colors "^1.4.0"
glob "^7.1.6"
jpeg-js "^0.4.2"
piexifjs "^1.0.6"
yargs-parser "^20.2.1"
jpeg-js@^0.4.1, jpeg-js@^0.4.2:
version "0.4.4"
resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.4.tgz#a9f1c6f1f9f0fa80cdb3484ed9635054d28936aa"
integrity sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==
@ -4904,6 +4940,11 @@ onetime@^5.1.0, onetime@^5.1.2:
dependencies:
mimic-fn "^2.1.0"
opencollective-postinstall@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259"
integrity sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==
opener@^1.5.1:
version "1.5.2"
resolved "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz"
@ -5059,7 +5100,7 @@ peek-readable@^4.0.1:
integrity sha512-7qmhptnR0WMSpxT5rMHG9bW/mYSR1uqaPFj2MHvT+y/aOUu6msJijpKt5SkTDKySwg65OWG2JwTMBlgcbwMHrQ==
"photoswipe@file:./thirdparty/photoswipe":
version "4.1.4"
version "4.1.6"
picocolors@^1.0.0:
version "1.0.0"
@ -5531,7 +5572,7 @@ regenerator-runtime@^0.11.0:
resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz"
integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.7:
regenerator-runtime@^0.13.3, regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.7:
version "0.13.9"
resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz"
integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==
@ -5600,6 +5641,11 @@ resolve-from@^4.0.0:
resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz"
integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
resolve-url@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
integrity sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==
resolve@^1.10.0, resolve@^1.14.2, resolve@^1.20.0:
version "1.20.0"
resolved "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz"
@ -6243,8 +6289,27 @@ terser@^4.6.2:
source-map "~0.6.1"
source-map-support "~0.5.12"
tesseract.js-core@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/tesseract.js-core/-/tesseract.js-core-2.2.0.tgz#6ef78051272a381969fac3e45a226e85022cffef"
integrity sha512-a8L+OJTbUipBsEDsJhDPlnLB0TY1MkTZqw5dqUwmiDSjUzwvU7HWLg/2+WDRulKUi4LE+7PnHlaBlW0k+V0U0w==
"tesseract.js@file:./thirdparty/tesseract":
version "0.0.0"
version "2.1.5"
dependencies:
blueimp-load-image "^3.0.0"
bmp-js "^0.1.0"
file-type "^12.4.1"
idb-keyval "^3.2.0"
is-electron "^2.2.0"
is-url "^1.2.4"
jpeg-autorotate "^7.1.1"
node-fetch "^2.6.0"
opencollective-postinstall "^2.0.2"
regenerator-runtime "^0.13.3"
resolve-url "^0.2.1"
tesseract.js-core "^2.2.0"
zlibjs "^0.3.1"
text-table@^0.2.0:
version "0.2.0"
@ -6888,6 +6953,11 @@ yaml@^1.10.0:
resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz"
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
yargs-parser@^20.2.1:
version "20.2.9"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
yargs@~3.10.0:
version "3.10.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1"
@ -6910,3 +6980,8 @@ yup@^0.29.3:
property-expr "^2.0.2"
synchronous-promise "^2.0.13"
toposort "^2.0.2"
zlibjs@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/zlibjs/-/zlibjs-0.3.1.tgz#50197edb28a1c42ca659cc8b4e6a9ddd6d444554"
integrity sha512-+J9RrgTKOmlxFSDHo0pI1xM6BLVUv+o0ZT9ANtCxGkjIVCCUdx9alUF8Gm+dGLKbkkkidWIHFDZHDMpfITt4+w==