Skip to content

Commit

Permalink
feat(epub-view): epub viewer with persisted location (#1164)
Browse files Browse the repository at this point in the history
Co-authored-by: Andrejs <Leitans>
  • Loading branch information
dr-leevsey authored Jul 11, 2024
1 parent 6bd4bf1 commit 0130c3b
Show file tree
Hide file tree
Showing 8 changed files with 215 additions and 17 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@
"react-localization": "^1.0.15",
"react-markdown": "^7.1.1",
"react-number-format": "^5.1.2",
"react-reader": "^2.0.10",
"react-redux": "^8.0.5",
"react-router-dom": "^6.9.0",
"react-textarea-autosize": "^8.2.0",
Expand Down
8 changes: 4 additions & 4 deletions src/components/ContentItem/contentItem.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// TODO: refactor needed
import React, { useEffect, useState } from 'react';
import { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { $TsFixMe } from 'src/types/tsfix';
import { LinksType } from 'src/containers/Search/types';
import useQueueIpfsContent from 'src/hooks/useQueueIpfsContent';
import type {
IpfsContentType,
IPFSContentDetails,
IpfsContentType,
} from 'src/services/ipfs/types';
import { LinksType } from 'src/containers/Search/types';
import { $TsFixMe } from 'src/types/tsfix';

import { parseArrayLikeToDetails } from 'src/services/ipfs/utils/content';

Expand Down
49 changes: 49 additions & 0 deletions src/components/EPubView/EPubView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React, {
CSSProperties,
ComponentProps,
useCallback,
useMemo,
} from 'react';
import { ReactReader } from 'react-reader';
import useEPubLocation from 'src/hooks/useEPubLocation';

interface IProps {
url: string;
search?: boolean;
style?: CSSProperties;
}

type EpubInitOptions = ComponentProps<typeof ReactReader>['epubInitOptions'];
const epubInitOptions: EpubInitOptions = { openAs: 'epub' };

function EPubView({ url, search, style }: IProps) {
const [location, setLocation] = useEPubLocation(url);
const currentLocation = location && !search ? location : 0;

const currentStyle = useMemo(
() => ({ height: search ? '300px' : '60vh', ...style }),
[style, search]
);

const onLocationChange = useCallback(
(loc: string) => {
if (!search) {
setLocation(loc);
}
},
[search, setLocation]
);

return (
<div style={currentStyle}>
<ReactReader
url={url}
location={currentLocation}
locationChanged={onLocationChange}
epubInitOptions={epubInitOptions}
/>
</div>
);
}

export default React.memo(EPubView);
19 changes: 14 additions & 5 deletions src/components/contentIpfs/contentIpfs.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { IPFSContentDetails, IPFSContentMaybe } from 'src/services/ipfs/types';
import { CYBER_GATEWAY } from 'src/constants/config';
import { CYBER_GATEWAY_URL } from 'src/services/ipfs/config';
import { IPFSContentDetails, IPFSContentMaybe } from 'src/services/ipfs/types';
import EPubView from '../EPubView/EPubView';
import Pdf from '../PDF';
import TextMarkdown from '../TextMarkdown';
import VideoPlayerGatewayOnly from '../VideoPlayer/VideoPlayerGatewayOnly';
import Audio from './component/Audio/Audio';
import GatewayContent from './component/gateway';
import TextMarkdown from '../TextMarkdown';
import LinkHttp from './component/link';
import Pdf from '../PDF';
import Img from './component/img';
import Audio from './component/Audio/Audio';
import LinkHttp from './component/link';

function OtherItem({
content,
Expand Down Expand Up @@ -77,6 +79,12 @@ function ContentIpfs({ details, content, cid, search }: ContentTabProps) {
{contentType === 'link' && (
<LinkHttp url={details.content!} preview={search} />
)}
{contentType === 'epub' && (
<EPubView
url={`${CYBER_GATEWAY_URL}/ipfs/${cid}`}
search={search}
/>
)}
{contentType === 'other' && (
<OtherItem search={search} cid={cid} content={details.content} />
)}
Expand All @@ -85,4 +93,5 @@ function ContentIpfs({ details, content, cid, search }: ContentTabProps) {
</div>
);
}

export default ContentIpfs;
47 changes: 47 additions & 0 deletions src/hooks/useEPubLocation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/* eslint-disable import/prefer-default-export */
import { useCallback } from 'react';
import type { EpubView } from 'react-reader';

const epubKey = 'cyb:epub';

const getEPubMap = (): Record<string, EpubView['location']> => {
try {
const epubMapString = localStorage.getItem(epubKey) || '';
const epubMap = JSON.parse(epubMapString);

return epubMap;
} catch (error) {
console.error('Failed to parse epub locations map:', error);

return {};
}
};

const getLocation = (url: string) => {
const epubMap = getEPubMap();

return epubMap[url] ?? null;
};

const getSetEPubLocation =
(url: string) => (location: EpubView['location']) => {
const epubMap = getEPubMap();

try {
epubMap[url] = location;
localStorage.setItem(epubKey, JSON.stringify(epubMap));
} catch (error) {
console.error('Failed to save EPub location:', error);
}
};

const useEPubLocation = (
url: string
): [EpubView['location'], (url: string) => void] => {
const currentLocation = getLocation(url);
const setEPubLocation = useCallback(getSetEPubLocation(url), [url]);

return [currentLocation, setEPubLocation];
};

export default useEPubLocation;
1 change: 1 addition & 0 deletions src/services/ipfs/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export type IpfsContentType =
| 'video'
| 'audio'
| 'html'
| 'epub'
| 'other';

export type IPFSContentDetails =
Expand Down
14 changes: 7 additions & 7 deletions src/services/ipfs/utils/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,18 @@ export const detectContentType = (
if (mime.includes('audio')) {
return 'audio';
}

if (mime.includes('epub')) {
return 'epub';
}
}

return 'other';
};

const basic = /\s?<!doctype html>|(<html\b[^>]*>|<body\b[^>]*>|<x-[^>]+>)+/i;

function isHtml(string) {
function isHtml(string: string) {
const newString = string.trim().slice(0, 1000);
return basic.test(newString);
}
Expand All @@ -55,30 +60,25 @@ export const chunksToBlob = (
// eslint-disable-next-line import/no-unused-modules, import/prefer-default-export
export const parseArrayLikeToDetails = async (
content: IPFSContentMaybe,
// rawDataResponse: Uint8ArrayLike | undefined,
// mime: string | undefined,
cid: string,
onProgress?: onProgressCallback
): Promise<IPFSContentDetails> => {
try {
// console.log('------parseArrayLikeToDetails', cid, content);
const mime = content?.meta?.mime;
const response: IPFSContentDetails = {
link: `/ipfs/${cid}`,
gateway: false,
cid,
};
const initialType = detectContentType(mime);
if (['video', 'audio'].indexOf(initialType) > -1) {
if (['video', 'audio', 'epub'].indexOf(initialType) > -1) {
return { ...response, type: initialType, gateway: true };
}

const rawData = content?.result
? await getResponseResult(content.result, onProgress)
: undefined;

// console.log(rawData);

if (!mime) {
response.text = `Can't detect MIME for ${cid.toString()}`;
response.gateway = true; // ???
Expand Down
Loading

0 comments on commit 0130c3b

Please sign in to comment.