mirror of
https://github.com/lrnselfreliance/wrolpi.git
synced 2026-10-25 18:40:06 -04:00
Renaming examples to avoid grouping of pdf with ebooks. Reworking URLs for media files to avoid escaping url safe characters.
This commit is contained in:
parent
b481334e1a
commit
44ebc876fa
21 changed files with 277 additions and 186 deletions
|
|
@ -81,18 +81,18 @@ export default function App() {
|
|||
useEventsInterval();
|
||||
|
||||
return <ThemeWrapper>
|
||||
<FilePreviewWrapper>
|
||||
{/* Context and style to handle switching between mobile/computer. */}
|
||||
<style>{mediaStyles}</style>
|
||||
{/* Toasts can be on any page. */}
|
||||
<SemanticToastContainer position='top-right'/>
|
||||
<MediaContextProvider>
|
||||
<TagsContext.Provider value={tagsValue}>
|
||||
<TagsContext.Provider value={tagsValue}>
|
||||
<FilePreviewWrapper>
|
||||
{/* Context and style to handle switching between mobile/computer. */}
|
||||
<style>{mediaStyles}</style>
|
||||
{/* Toasts can be on any page. */}
|
||||
<SemanticToastContainer position='top-right'/>
|
||||
<MediaContextProvider>
|
||||
<StatusContext.Provider value={statusValue}>
|
||||
<RouterProvider router={router}/>
|
||||
</StatusContext.Provider>
|
||||
</TagsContext.Provider>
|
||||
</MediaContextProvider>
|
||||
</FilePreviewWrapper>
|
||||
</MediaContextProvider>
|
||||
</FilePreviewWrapper>
|
||||
</TagsContext.Provider>
|
||||
</ThemeWrapper>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,13 +22,21 @@ import {
|
|||
TableRow,
|
||||
} from "semantic-ui-react";
|
||||
import {contrastingColor, HelpPopup} from "./components/Common";
|
||||
import {Button, Segment} from "./components/Theme";
|
||||
import {Segment} from "./components/Theme";
|
||||
import _ from "lodash";
|
||||
import {Link} from "react-router-dom";
|
||||
import {HexColorPicker} from "react-colorful";
|
||||
|
||||
export const TagsContext = React.createContext({
|
||||
NameToTagLabel: null,
|
||||
TagsGroup: null,
|
||||
TagsLinkGroup: null,
|
||||
fetchTags: null,
|
||||
findTagByName: null,
|
||||
tagNames: [],
|
||||
tags: [],
|
||||
});
|
||||
|
||||
const DEFAULT_TAG_COLOR = '#000000';
|
||||
export const TagsContext = React.createContext({tags: [], fetchTags: null, findTagByName: null});
|
||||
|
||||
export function useTags() {
|
||||
const [tags, setTags] = React.useState(null);
|
||||
|
|
@ -94,9 +102,9 @@ export function useTags() {
|
|||
}
|
||||
return <Label.Group tag>
|
||||
{tagNames.map(i =>
|
||||
<Link key={i} to={`/?tag=${i}`} style={{marginLeft: '0.3em', marginRight: '0.3em'}}>
|
||||
<a key={i} href={`/?tag=${i}`} style={{marginLeft: '0.3em', marginRight: '0.3em'}}>
|
||||
<NameToTagLabel name={i} {...props}/>
|
||||
</Link>
|
||||
</a>
|
||||
)}
|
||||
</Label.Group>
|
||||
}
|
||||
|
|
@ -116,7 +124,7 @@ function EditTagLabel({tag, onDelete, onEdit}) {
|
|||
|
||||
return <TableRow>
|
||||
<TableCell>
|
||||
<SButton color='red' onClick={() => setConfirmDeleteOpen(true)} icon='close'/>
|
||||
<SButton color='red' onClick={() => setConfirmDeleteOpen(true)} icon='trash'/>
|
||||
<Confirm
|
||||
id={`confirm${name}`}
|
||||
open={confirmDeleteOpen}
|
||||
|
|
@ -232,8 +240,7 @@ function EditTagsModal() {
|
|||
|
||||
export function TagsModal({fileGroup, onClick}) {
|
||||
const {tags: usedTags} = fileGroup;
|
||||
const {TagsGroup} = useTags();
|
||||
const {tags: availableTags} = React.useContext(TagsContext);
|
||||
const {tags: availableTags, TagsGroup} = React.useContext(TagsContext);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
|
|
@ -241,7 +248,9 @@ export function TagsModal({fileGroup, onClick}) {
|
|||
setLoading(true);
|
||||
try {
|
||||
await addTag(fileGroup, name);
|
||||
await onClick();
|
||||
if (onClick) {
|
||||
await onClick();
|
||||
}
|
||||
console.debug('Added tag');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
|
|
@ -252,7 +261,9 @@ export function TagsModal({fileGroup, onClick}) {
|
|||
setLoading(true);
|
||||
try {
|
||||
await removeTag(fileGroup, name);
|
||||
await onClick();
|
||||
if (onClick) {
|
||||
await onClick();
|
||||
}
|
||||
console.debug('Removed tag');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
|
|
@ -294,7 +305,7 @@ export function TagsModal({fileGroup, onClick}) {
|
|||
</Modal>
|
||||
|
||||
return <>
|
||||
<Button icon='tag' onClick={() => setOpen(true)}/>
|
||||
<SButton icon='tag' onClick={() => setOpen(true)}/>
|
||||
{modal}
|
||||
</>
|
||||
}
|
||||
|
|
@ -302,7 +313,11 @@ export function TagsModal({fileGroup, onClick}) {
|
|||
export const taggedImageLabel = {corner: 'left', icon: 'tag', color: 'green'};
|
||||
|
||||
export const TagsDisplay = ({fileGroup, onClick}) => {
|
||||
const {TagsLinkGroup} = useTags();
|
||||
const {TagsLinkGroup} = React.useContext(TagsContext);
|
||||
|
||||
if (!fileGroup || !TagsLinkGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
return <Grid>
|
||||
<Grid.Row>
|
||||
|
|
@ -320,7 +335,7 @@ export const TagsDisplay = ({fileGroup, onClick}) => {
|
|||
}
|
||||
|
||||
export const TagsDashboard = () => {
|
||||
const {tagNames, TagsLinkGroup} = useTags();
|
||||
const {tagNames, TagsLinkGroup} = React.useContext(TagsContext);
|
||||
|
||||
let availableTagsGroup = <SLoader active inline/>;
|
||||
if (tagNames && tagNames.length) {
|
||||
|
|
|
|||
|
|
@ -18,8 +18,10 @@ import {
|
|||
CardLink,
|
||||
cardTitleWrapper,
|
||||
defaultSearchOrder,
|
||||
encodeMediaPath,
|
||||
ExternalCardLink,
|
||||
FileIcon,
|
||||
findPosterPath,
|
||||
HelpHeader,
|
||||
isoDatetimeToString,
|
||||
mimetypeColor,
|
||||
|
|
@ -68,8 +70,8 @@ function ArchivePage() {
|
|||
|
||||
const {data} = archiveFile;
|
||||
|
||||
const singlefileUrl = data.singlefile_path ? `/media/${encodeURIComponent(data.singlefile_path)}` : null;
|
||||
const screenshotUrl = data.screenshot_path ? `/media/${encodeURIComponent(data.screenshot_path)}` : null;
|
||||
const singlefileUrl = data.singlefile_path ? `/media/${encodeMediaPath(data.singlefile_path)}` : null;
|
||||
const screenshotUrl = data.screenshot_path ? `/media/${encodeMediaPath(data.screenshot_path)}` : null;
|
||||
const readabilityUrl = data.readability_path;
|
||||
|
||||
const singlefileButton = <ExternalCardLink to={singlefileUrl}>
|
||||
|
|
@ -178,8 +180,8 @@ export function ArchiveCard({file}) {
|
|||
const {s} = useContext(ThemeContext);
|
||||
const {data} = file;
|
||||
|
||||
const imageSrc = data.screenshot_path ? `/media/${encodeURIComponent(data.screenshot_path)}` : null;
|
||||
const singlefileUrl = data.singlefile_path ? `/media/${encodeURIComponent(data.singlefile_path)}` : null;
|
||||
const imageSrc = data.screenshot_path ? `/media/${encodeMediaPath(data.screenshot_path)}` : null;
|
||||
const singlefileUrl = data.singlefile_path ? `/media/${encodeMediaPath(data.singlefile_path)}` : null;
|
||||
|
||||
let screenshot = <CardIcon><FileIcon file={file}/></CardIcon>;
|
||||
const imageLabel = file.tags && file.tags.length ? taggedImageLabel : null;
|
||||
|
|
@ -436,7 +438,8 @@ export function ArchiveRowCells({file}) {
|
|||
const {data} = file;
|
||||
|
||||
const archiveUrl = `/archive/${data.id}`;
|
||||
const posterUrl = data.screenshot_path ? `/media/${encodeURIComponent(data.screenshot_path)}` : null;
|
||||
const posterPath = findPosterPath(file);
|
||||
const posterUrl = posterPath ? `/media/${encodeMediaPath(posterPath)}` : null;
|
||||
|
||||
let poster;
|
||||
if (posterUrl) {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import React, {useContext, useEffect, useState} from "react";
|
||||
import {Card, Container, IconGroup, Image, Input, Modal, Pagination} from 'semantic-ui-react';
|
||||
import {Card, Container, IconGroup, Input, Modal, Pagination} from 'semantic-ui-react';
|
||||
import {Link, NavLink, useNavigate} from "react-router-dom";
|
||||
import Message from "semantic-ui-react/dist/commonjs/collections/Message";
|
||||
import {useDirectories, useDownloaders, useHotspot, useSettings, useThrottle} from "../hooks/customHooks";
|
||||
import {Media, StatusContext, ThemeContext} from "../contexts/contexts";
|
||||
import {Button, darkTheme, Form, Header, Icon, lightTheme, Menu, Popup, Statistic} from "./Theme";
|
||||
import {Button, CardIcon, darkTheme, Form, Header, Icon, lightTheme, Menu, Popup, Statistic} from "./Theme";
|
||||
import {FilePreviewContext} from "./FilePreview";
|
||||
import _ from "lodash";
|
||||
|
||||
export const API_URI = process.env && process.env.REACT_APP_API_URI ? process.env.REACT_APP_API_URI : `${window.location.protocol}//${window.location.host}/api`;
|
||||
export const VIDEOS_API = `${API_URI}/videos`;
|
||||
|
|
@ -441,23 +442,77 @@ export function CardGroupCentered(props) {
|
|||
</div>
|
||||
}
|
||||
|
||||
export function CardPosterLink({to, poster_url, imageLabel, external = false}) {
|
||||
export function findPosterPath(file) {
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
const {files, poster_path, cover_path, screenshot_path, video} = file;
|
||||
if (poster_path) {
|
||||
return poster_path;
|
||||
}
|
||||
if (cover_path) {
|
||||
return cover_path;
|
||||
}
|
||||
if (screenshot_path) {
|
||||
return screenshot_path;
|
||||
}
|
||||
if (video && video['poster_path']) {
|
||||
// file is a video model.
|
||||
return video['poster_path'];
|
||||
}
|
||||
if (!_.isEmpty(files)) {
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
if (file['mimetype'] && file['mimetype'].startsWith('image/')) {
|
||||
// Found an image file, use that as the poster.
|
||||
return file['path'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function CardPoster({to, file}) {
|
||||
const {s} = useContext(ThemeContext);
|
||||
// Used to center posters in CardIcon.
|
||||
const style = {display: 'flex', justifyContent: 'center', ...s['style']};
|
||||
const image = <Image src={poster_url} label={imageLabel}
|
||||
style={{maxHeight: '163px', width: 'auto'}}
|
||||
/>;
|
||||
if (external === true && to) {
|
||||
return <a href={to} target='_blank' rel='noreferrer' style={style}>
|
||||
{image}
|
||||
</a>
|
||||
} else if (to) {
|
||||
|
||||
const cardTagIcon = <div className="ui green left corner label">
|
||||
<i aria-hidden="true" className="tag icon"></i>
|
||||
</div>;
|
||||
let imageLabel = !_.isEmpty(file.tags) ? cardTagIcon : null;
|
||||
|
||||
let posterPath = findPosterPath(file);
|
||||
|
||||
if (!posterPath) {
|
||||
// No poster, use a FileIcon.
|
||||
return <PreviewLink file={file}>
|
||||
<CardIcon>
|
||||
{imageLabel}
|
||||
<FileIcon file={file}/>
|
||||
</CardIcon>
|
||||
</PreviewLink>
|
||||
}
|
||||
|
||||
posterPath = `/media/${encodeMediaPath(posterPath)}`;
|
||||
|
||||
const image = <>
|
||||
{/* Replicate <Image label/> but with maxHeight applied to image */}
|
||||
{imageLabel}
|
||||
<img alt='poster' src={posterPath} style={{maxHeight: '163px', width: 'auto'}}/>
|
||||
</>;
|
||||
|
||||
if (to) {
|
||||
// Link using React Router.
|
||||
return <Link to={to} style={style}>
|
||||
{image}
|
||||
</Link>
|
||||
} else {
|
||||
return <div style={style}>{image}</div>;
|
||||
// Preview the file.
|
||||
return <div style={style}>
|
||||
<PreviewLink file={file}>
|
||||
{image}
|
||||
</PreviewLink>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -840,7 +895,7 @@ export const filterToMimetypes = (filter) => {
|
|||
} else if (filter === 'image') {
|
||||
return ['image'];
|
||||
} else if (filter === 'zip') {
|
||||
return ['application/zip'];
|
||||
return ['application/zip', 'application/zlib', 'application/x-bzip2', 'application/x-xz', 'application/x-bzip', 'application/x-bzip2', 'application/gzip', 'application/vnd.rar', 'application/x-tar', 'application/x-7z-compressed'];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -874,3 +929,12 @@ function hexToRGBArray(color) {
|
|||
export function contrastingColor(color) {
|
||||
return (luma(color) >= 120) ? '#000000' : '#dddddd';
|
||||
}
|
||||
|
||||
export const encodeMediaPath = (path) => {
|
||||
// Replace % first to avoid replacing the other replacements.
|
||||
path = path.replaceAll('%', '%25');
|
||||
|
||||
path = path.replaceAll('#', '%23');
|
||||
path = path.replaceAll(' ', '%20');
|
||||
return path
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,6 @@ class Downloader extends React.Component {
|
|||
}
|
||||
|
||||
handleInputChange = async (event, {name, value}) => {
|
||||
console.log(name, value);
|
||||
this.setState({[name]: value}, this.validateUrls);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import React from "react";
|
|||
import _ from "lodash";
|
||||
import {
|
||||
Button as SButton,
|
||||
Grid,
|
||||
Header as SHeader,
|
||||
Image,
|
||||
Modal,
|
||||
|
|
@ -9,33 +10,36 @@ import {
|
|||
ModalContent,
|
||||
ModalHeader
|
||||
} from "semantic-ui-react";
|
||||
import {TagsDisplay} from "../Tags";
|
||||
import {Media} from "../contexts/contexts";
|
||||
import {encodeMediaPath} from "./Common";
|
||||
|
||||
function getMediaPathURL(file) {
|
||||
if (file['primary_path']) {
|
||||
return `/media/${encodeURIComponent(file['primary_path'])}`;
|
||||
function getMediaPathURL(previewFile) {
|
||||
if (previewFile['primary_path']) {
|
||||
return `/media/${encodeMediaPath(previewFile['primary_path'])}`;
|
||||
} else {
|
||||
return `/media/${encodeURIComponent(file['path'])}`;
|
||||
return `/media/${encodeMediaPath(previewFile['path'])}`;
|
||||
}
|
||||
}
|
||||
|
||||
function getDownloadPathURL(file) {
|
||||
if (file['primary_path']) {
|
||||
return `/download/${encodeURIComponent(file['primary_path'])}`;
|
||||
function getDownloadPathURL(previewFile) {
|
||||
if (previewFile['primary_path']) {
|
||||
return `/download/${encodeMediaPath(previewFile['primary_path'])}`;
|
||||
} else {
|
||||
return `/download/${encodeURIComponent(file['path'])}`;
|
||||
return `/download/${encodeMediaPath(previewFile['path'])}`;
|
||||
}
|
||||
}
|
||||
|
||||
function getEpubViewerURL(file) {
|
||||
if (file['primary_path']) {
|
||||
return `/epub.html?url=download/${encodeURIComponent(file['primary_path'])}`;
|
||||
function getEpubViewerURL(previewFile) {
|
||||
if (previewFile['primary_path']) {
|
||||
return `/epub.html?url=download/${encodeMediaPath(previewFile['primary_path'])}`;
|
||||
} else {
|
||||
return `/epub.html?url=download/${encodeURIComponent(file['path'])}`;
|
||||
return `/epub.html?url=download/${encodeMediaPath(previewFile['path'])}`;
|
||||
}
|
||||
}
|
||||
|
||||
function getIframeModal(path) {
|
||||
const url = getMediaPathURL(path);
|
||||
function getIframeModal(previewFile) {
|
||||
const url = getMediaPathURL(previewFile);
|
||||
return <ModalContent>
|
||||
<div className='full-height'>
|
||||
<iframe title='textModal' src={url}
|
||||
|
|
@ -46,9 +50,11 @@ function getIframeModal(path) {
|
|||
</ModalContent>
|
||||
}
|
||||
|
||||
function getImageModal(path) {
|
||||
const url = getMediaPathURL(path);
|
||||
const name = path['path'].replace(/^.*[\\\/]/, '');
|
||||
function getImageModal(previewFile) {
|
||||
const path = previewFile.primary_path ?? previewFile.path;
|
||||
const url = getMediaPathURL(previewFile);
|
||||
// Get the file name from the absolute path.
|
||||
const name = path.replace(/^.*[\\\/]/, '');
|
||||
return <React.Fragment>
|
||||
<ModalHeader>
|
||||
{name}
|
||||
|
|
@ -61,8 +67,8 @@ function getImageModal(path) {
|
|||
</React.Fragment>
|
||||
}
|
||||
|
||||
function getEpubModal(path) {
|
||||
const downloadURL = getDownloadPathURL(path);
|
||||
function getEpubModal(previewFile) {
|
||||
const downloadURL = getDownloadPathURL(previewFile);
|
||||
const viewerUrl = `/epub.html?url=${downloadURL}`;
|
||||
return <ModalContent>
|
||||
<div className='full-height'>
|
||||
|
|
@ -74,10 +80,11 @@ function getEpubModal(path) {
|
|||
</ModalContent>
|
||||
}
|
||||
|
||||
function getVideoModal(path) {
|
||||
const url = getMediaPathURL(path);
|
||||
const name = path['path'].replace(/^.*[\\\/]/, '');
|
||||
const type = path['mimetype'] ? path['mimetype'] : 'video/mp4';
|
||||
function getVideoModal(previewFile) {
|
||||
const url = getMediaPathURL(previewFile);
|
||||
const path = previewFile.primary_path ?? previewFile.path;
|
||||
const name = path.replace(/^.*[\\\/]/, '');
|
||||
const type = previewFile.mimetype ? previewFile.mimetype : 'video/mp4';
|
||||
return <React.Fragment>
|
||||
<ModalHeader>
|
||||
{name}
|
||||
|
|
@ -96,11 +103,12 @@ function getVideoModal(path) {
|
|||
</React.Fragment>
|
||||
}
|
||||
|
||||
function getAudioModal(path) {
|
||||
const url = getMediaPathURL(path);
|
||||
const name = path['path'].replace(/^.*[\\\/]/, '');
|
||||
function getAudioModal(previewFile) {
|
||||
const url = getMediaPathURL(previewFile);
|
||||
const path = previewFile.primary_path ?? previewFile.path;
|
||||
const name = path.replace(/^.*[\\\/]/, '');
|
||||
// Some audio files may not have the correct mimetype. Trust the suffix identifies an audio file.
|
||||
const type = path['mimetype'] !== 'application/octet-stream' ? path['mimetype'] : 'audio/mpeg';
|
||||
const type = previewFile['mimetype'] !== 'application/octet-stream' ? previewFile['mimetype'] : 'audio/mpeg';
|
||||
return <React.Fragment>
|
||||
<ModalHeader>
|
||||
{name}
|
||||
|
|
@ -132,6 +140,16 @@ export function FilePreviewWrapper({children}) {
|
|||
};
|
||||
|
||||
function setModalContent(content, url, downloadURL) {
|
||||
const openButton = <SButton color='blue' as='a' href={url}>Open</SButton>;
|
||||
const closeButton = <SButton onClick={() => setPreviewFile(null)}>Close</SButton>;
|
||||
const tagsDisplay = <TagsDisplay fileGroup={previewFile}/>;
|
||||
let downloadButton;
|
||||
if (downloadURL) {
|
||||
downloadButton = <SButton color='yellow' as='a' href={downloadURL} floated='left'>
|
||||
Download
|
||||
</SButton>;
|
||||
}
|
||||
|
||||
setPreviewModal(<Modal closeIcon
|
||||
size='fullscreen'
|
||||
open={true}
|
||||
|
|
@ -139,10 +157,28 @@ export function FilePreviewWrapper({children}) {
|
|||
>
|
||||
{content}
|
||||
<ModalActions>
|
||||
{downloadURL &&
|
||||
<SButton color='yellow' onClick={() => window.open(downloadURL)} floated='left'>Download</SButton>}
|
||||
<SButton color='blue' onClick={() => window.open(url)}>Open</SButton>
|
||||
<SButton onClick={() => setPreviewFile(null)}>Close</SButton>
|
||||
<Media at='mobile'>
|
||||
<Grid>
|
||||
<Grid.Row>
|
||||
<Grid.Column width={6}>{downloadButton}</Grid.Column>
|
||||
<Grid.Column width={5}>{openButton}</Grid.Column>
|
||||
<Grid.Column width={5}>{closeButton}</Grid.Column>
|
||||
</Grid.Row>
|
||||
<Grid.Row>
|
||||
<Grid.Column width={16}>{tagsDisplay}</Grid.Column>
|
||||
</Grid.Row>
|
||||
</Grid>
|
||||
</Media>
|
||||
<Media greaterThanOrEqual='tablet'>
|
||||
<Grid>
|
||||
<Grid.Row>
|
||||
<Grid.Column width={2}>{downloadButton}</Grid.Column>
|
||||
<Grid.Column width={10}>{tagsDisplay}</Grid.Column>
|
||||
<Grid.Column width={2}>{openButton}</Grid.Column>
|
||||
<Grid.Column width={2}>{closeButton}</Grid.Column>
|
||||
</Grid.Row>
|
||||
</Grid>
|
||||
</Media>
|
||||
</ModalActions>
|
||||
</Modal>);
|
||||
}
|
||||
|
|
@ -173,6 +209,8 @@ export function FilePreviewWrapper({children}) {
|
|||
} else if (mimetype.startsWith('application/epub')) {
|
||||
const viewerURL = getEpubViewerURL(previewFile);
|
||||
setModalContent(getEpubModal(previewFile), viewerURL, downloadURL);
|
||||
} else if (mimetype.startsWith('application/pdf')) {
|
||||
setModalContent(getIframeModal(previewFile), url, downloadURL);
|
||||
} else if (mimetype.startsWith('image/')) {
|
||||
setModalContent(getImageModal(previewFile), url);
|
||||
} else if (mimetype.startsWith('application/octet-stream') && lowerPath.endsWith('.mp3')) {
|
||||
|
|
|
|||
|
|
@ -14,10 +14,12 @@ import {
|
|||
} from "semantic-ui-react";
|
||||
import {
|
||||
CardGroupCentered,
|
||||
CardPosterLink,
|
||||
CardPoster,
|
||||
cardTitleWrapper,
|
||||
encodeMediaPath,
|
||||
ExternalCardLink,
|
||||
FileIcon,
|
||||
findPosterPath,
|
||||
humanFileSize,
|
||||
isoDatetimeToString,
|
||||
mimetypeColor,
|
||||
|
|
@ -40,33 +42,24 @@ import _ from 'lodash';
|
|||
import {FileBrowser} from "./FileBrowser";
|
||||
import {refreshDirectoryFiles, refreshFiles} from "../api";
|
||||
import {useSubscribeEventName} from "../Events";
|
||||
import {FilePreviewContext} from "./FilePreview";
|
||||
import {taggedImageLabel, TagsContext} from "../Tags";
|
||||
import {TagsContext} from "../Tags";
|
||||
|
||||
function EbookCard({file}) {
|
||||
const {s} = useContext(ThemeContext);
|
||||
let {data} = file;
|
||||
|
||||
const downloadUrl = `/download/${encodeURIComponent(file.primary_path)}`;
|
||||
const downloadUrl = `/download/${encodeMediaPath(file.primary_path)}`;
|
||||
const isEpub = file['mimetype'].startsWith('application/epub');
|
||||
const viewerUrl = isEpub ? `/epub.html?url=${downloadUrl}` : null;
|
||||
|
||||
let cover = <FileIcon file={file}/>;
|
||||
if (data && data.cover_path) {
|
||||
const coverSrc = `/media/${encodeURIComponent(data.cover_path)}`;
|
||||
cover = <CardPosterLink poster_url={coverSrc}/>;
|
||||
}
|
||||
|
||||
const color = mimetypeColor(file.mimetype);
|
||||
return <Card color={color}>
|
||||
<PreviewLink file={file}>
|
||||
{cover}
|
||||
</PreviewLink>
|
||||
<CardPoster file={file} preview={true}/>
|
||||
<CardContent {...s}>
|
||||
<CardHeader>
|
||||
<Container textAlign='left'>
|
||||
<ExternalCardLink to={viewerUrl || downloadUrl}>
|
||||
{data ? data.title : file.title}
|
||||
{cardTitleWrapper(data ? data.title : file.title)}
|
||||
</ExternalCardLink>
|
||||
</Container>
|
||||
</CardHeader>
|
||||
|
|
@ -81,22 +74,11 @@ function EbookCard({file}) {
|
|||
|
||||
function ImageCard({file}) {
|
||||
const {s} = useContext(ThemeContext);
|
||||
const {setPreviewFile} = React.useContext(FilePreviewContext);
|
||||
const url = `/media/${encodeURIComponent(file.primary_path)}`;
|
||||
|
||||
let poster = <FileIcon file={file}/>;
|
||||
if (file.size && file.size < 50000000) {
|
||||
// Image is less than 5mb, use it.
|
||||
poster = <Image wrapped
|
||||
src={url}
|
||||
style={{position: 'relative', width: '100%'}}
|
||||
onClick={() => setPreviewFile(file)}
|
||||
/>
|
||||
}
|
||||
const url = `/media/${encodeMediaPath(file.primary_path)}`;
|
||||
|
||||
return <Card color={mimetypeColor(file.mimetype)}>
|
||||
<PreviewLink file={file}>
|
||||
{poster}
|
||||
<CardPoster file={file}/>
|
||||
</PreviewLink>
|
||||
<CardContent {...s}>
|
||||
<CardHeader>
|
||||
|
|
@ -114,18 +96,6 @@ function ImageCard({file}) {
|
|||
</Card>
|
||||
}
|
||||
|
||||
function findPosterPath(files) {
|
||||
if (_.isEmpty(files)) {
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
if (file['mimetype'] && file['mimetype'].startsWith('image/')) {
|
||||
return file['path'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function FileCard({file}) {
|
||||
const {s} = useContext(ThemeContext);
|
||||
|
||||
|
|
@ -143,27 +113,17 @@ function FileCard({file}) {
|
|||
return <EbookCard key={file['primary_path']} file={file}/>;
|
||||
}
|
||||
|
||||
const {data, tags} = file;
|
||||
const {data} = file;
|
||||
let author;
|
||||
if (data) {
|
||||
author = data['author'];
|
||||
}
|
||||
const downloadUrl = `/download/${encodeURIComponent(file.primary_path)}`;
|
||||
const downloadUrl = `/download/${encodeMediaPath(file.primary_path)}`;
|
||||
const color = mimetypeColor(file.mimetype);
|
||||
const size = file.size !== null && file.size !== undefined ? humanFileSize(file.size) : null;
|
||||
|
||||
const posterFile = findPosterPath(file['files']);
|
||||
let poster = <CardIcon><FileIcon file={file}/></CardIcon>;
|
||||
let imageLabel = !_.isEmpty(tags) ? taggedImageLabel : null;
|
||||
if (posterFile) {
|
||||
const posterUrl = `/media/${encodeURIComponent(posterFile)}`;
|
||||
poster = <CardPosterLink to={downloadUrl} poster_url={posterUrl} imageLabel={imageLabel}/>;
|
||||
}
|
||||
|
||||
return <Card color={color}>
|
||||
<PreviewLink file={file}>
|
||||
{poster}
|
||||
</PreviewLink>
|
||||
<CardPoster to={downloadUrl} file={file}/>
|
||||
<CardContent {...s}>
|
||||
<CardHeader>
|
||||
<ExternalCardLink to={downloadUrl}>
|
||||
|
|
@ -190,7 +150,7 @@ export function FileCards({files}) {
|
|||
}
|
||||
|
||||
function ImageRowCells({file}) {
|
||||
const url = `/media/${encodeURIComponent(file.primary_path)}`;
|
||||
const url = `/media/${encodeMediaPath(file.primary_path)}`;
|
||||
|
||||
let poster = <FileIcon file={file} size='large'/>;
|
||||
if (file.size && file.size < 50000000) {
|
||||
|
|
@ -214,11 +174,10 @@ function ImageRowCells({file}) {
|
|||
}
|
||||
|
||||
export function EbookRowCells({file}) {
|
||||
const {data} = file;
|
||||
|
||||
let cover = <CardIcon><FileIcon file={file}/></CardIcon>;
|
||||
if (data && data.cover_path) {
|
||||
const coverSrc = `/media/${encodeURIComponent(data.cover_path)}`;
|
||||
const posterPath = findPosterPath(file);
|
||||
if (posterPath) {
|
||||
const coverSrc = `/media/${encodeMediaPath(posterPath)}`;
|
||||
cover = <Image wrapped src={coverSrc} width='50px'/>;
|
||||
}
|
||||
|
||||
|
|
@ -229,6 +188,7 @@ export function EbookRowCells({file}) {
|
|||
</TableCell>
|
||||
<TableCell>
|
||||
<PreviewLink file={file}>
|
||||
<FileRowTagIcon file={file}/>
|
||||
{textEllipsis(file.title || file.stem)}
|
||||
</PreviewLink>
|
||||
</TableCell>
|
||||
|
|
@ -256,7 +216,8 @@ function FileRow({file}) {
|
|||
<center><FileIcon file={file} size='large'/></center>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<PreviewLink file={file}>{textEllipsis(file.title)}</PreviewLink>
|
||||
<FileRowTagIcon file={file}/>
|
||||
<PreviewLink file={file}>{textEllipsis(file.title || file.name || file.stem)}</PreviewLink>
|
||||
</TableCell>
|
||||
</React.Fragment>
|
||||
}
|
||||
|
|
@ -397,7 +358,7 @@ export function FilesView({
|
|||
}
|
||||
|
||||
export function TagsFilesGroupDropdown({onChange}) {
|
||||
// Creates a dropdown that the User can use to manipulate the tag query.
|
||||
// Creates dropdown that the User can use to manipulate the tag query.
|
||||
const {searchParams, updateQuery} = useQuery();
|
||||
const activeTag = searchParams.get('tag');
|
||||
const {tags} = React.useContext(TagsContext);
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ const links = [
|
|||
{text: 'Map', to: '/map', key: 'map'},
|
||||
{text: 'Files', to: '/files', key: 'files'},
|
||||
{text: 'Inventory', to: '/inventory', key: 'inventory'},
|
||||
{text: 'More', key: 'more',
|
||||
{
|
||||
text: 'More', key: 'more',
|
||||
links: [
|
||||
{to: '/more/otp', text: 'One Time Pad', end: true},
|
||||
{to: '/more/statistics', text: 'Statistics', end: true},
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import {Link, useNavigate, useParams} from "react-router-dom";
|
|||
import _ from "lodash";
|
||||
import {
|
||||
BackButton,
|
||||
encodeMediaPath,
|
||||
humanFileSize,
|
||||
humanNumber,
|
||||
isoDatetimeToString,
|
||||
|
|
@ -28,8 +29,8 @@ function videoFileLink(path, directory = false) {
|
|||
if (path) {
|
||||
const href = directory ?
|
||||
// Add / to end of directory.
|
||||
`${MEDIA_PATH}/${encodeURIComponent(path)}/` :
|
||||
`${MEDIA_PATH}/${encodeURIComponent(path)}`;
|
||||
`${MEDIA_PATH}/${encodeMediaPath(path)}/` :
|
||||
`${MEDIA_PATH}/${encodeMediaPath(path)}`;
|
||||
return <a href={href} target='_blank' rel='noopener noreferrer'>
|
||||
<pre>{path}</pre>
|
||||
</a>
|
||||
|
|
@ -78,11 +79,11 @@ function VideoPage({videoFile, prevFile, nextFile, fetchVideo, ...props}) {
|
|||
navigate(-1);
|
||||
}
|
||||
|
||||
let videoUrl = `${MEDIA_PATH}/${encodeURIComponent(video.video_path)}`;
|
||||
let downloadUrl = `/download/${encodeURIComponent(video.video_path)}`;
|
||||
let videoUrl = `${MEDIA_PATH}/${encodeMediaPath(video.video_path)}`;
|
||||
let downloadUrl = `/download/${encodeMediaPath(video.video_path)}`;
|
||||
|
||||
let posterUrl = video.poster_path ? `${MEDIA_PATH}/${encodeURIComponent(video.poster_path)}` : null;
|
||||
let captionsUrl = video.caption_path ? `${MEDIA_PATH}/${encodeURIComponent(video.caption_path)}` : null;
|
||||
let posterUrl = video.poster_path ? `${MEDIA_PATH}/${encodeMediaPath(video.poster_path)}` : null;
|
||||
let captionsUrl = video.caption_path ? `${MEDIA_PATH}/${encodeMediaPath(video.caption_path)}` : null;
|
||||
let vttCaptions = video.caption_path && video.caption_path.endsWith('.vtt');
|
||||
|
||||
let description = 'No description available.';
|
||||
|
|
|
|||
|
|
@ -2,12 +2,14 @@ import React, {Fragment, useContext, useEffect, useState} from 'react';
|
|||
import {Link, Route, Routes, useParams} from "react-router-dom";
|
||||
import {
|
||||
CardLink,
|
||||
CardPosterLink,
|
||||
CardPoster,
|
||||
cardTitleWrapper,
|
||||
defaultSearchOrder,
|
||||
defaultVideoOrder,
|
||||
Duration,
|
||||
encodeMediaPath,
|
||||
FileIcon,
|
||||
findPosterPath,
|
||||
isoDatetimeToString,
|
||||
mimetypeColor,
|
||||
PageContainer,
|
||||
|
|
@ -37,11 +39,10 @@ import {useChannel, useQuery, useSearchVideos, useVideo, useVideoStatistics} fro
|
|||
import {FileRowTagIcon, FilesView} from "./Files";
|
||||
import Grid from "semantic-ui-react/dist/commonjs/collections/Grid";
|
||||
import Icon from "semantic-ui-react/dist/commonjs/elements/Icon";
|
||||
import {Button, Card, CardIcon, Header, Loader, Placeholder, Segment, Statistic, StatisticGroup} from "./Theme";
|
||||
import {Button, Card, Header, Loader, Placeholder, Segment, Statistic, StatisticGroup} from "./Theme";
|
||||
import {deleteVideos} from "../api";
|
||||
import {ThemeContext} from "../contexts/contexts";
|
||||
import _ from "lodash";
|
||||
import {taggedImageLabel} from "../Tags";
|
||||
|
||||
export function VideoWrapper() {
|
||||
const {videoId} = useParams();
|
||||
|
|
@ -285,7 +286,7 @@ export function VideosRoute(props) {
|
|||
}
|
||||
|
||||
export function VideoCard({file}) {
|
||||
const {video, tags} = file;
|
||||
const {video} = file;
|
||||
const {s} = useContext(ThemeContext);
|
||||
|
||||
let video_url = `/videos/video/${video.id}`;
|
||||
|
|
@ -297,13 +298,8 @@ export function VideoCard({file}) {
|
|||
channel_url = `/videos/channel/${channel.id}/video`;
|
||||
video_url = `/videos/channel/${channel.id}/video/${video.id}`;
|
||||
}
|
||||
const poster_url = video.poster_path ? `/media/${encodeURIComponent(video.poster_path)}` : null;
|
||||
|
||||
let imageLabel = !_.isEmpty(tags) ? taggedImageLabel : null;
|
||||
let poster = <Link to={video_url}><CardIcon><FileIcon file={file}/></CardIcon></Link>;
|
||||
if (poster_url) {
|
||||
poster = <CardPosterLink to={video_url} poster_url={poster_url} imageLabel={imageLabel}/>;
|
||||
}
|
||||
let poster = <CardPoster to={video_url} file={file}/>;
|
||||
|
||||
const color = mimetypeColor(file.mimetype);
|
||||
return <Card color={color}>
|
||||
|
|
@ -333,7 +329,8 @@ export function VideoRowCells({file}) {
|
|||
const {video} = file;
|
||||
|
||||
let video_url = `/videos/video/${video.id}`;
|
||||
const poster_url = video.poster_path ? `/media/${encodeURIComponent(video.poster_path)}` : null;
|
||||
const poster_path = findPosterPath(file);
|
||||
const poster_url = poster_path ? `/media/${encodeMediaPath(poster_path)}` : null;
|
||||
|
||||
let poster;
|
||||
if (poster_url) {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,7 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
|
||||
<g fill="#61DAFB">
|
||||
<path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
|
||||
<circle cx="420.9" cy="296.5" r="45.7"/>
|
||||
<path d="M520.5 78.1z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
|
@ -1,13 +1,13 @@
|
|||
const reportWebVitals = onPerfEntry => {
|
||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||
getCLS(onPerfEntry);
|
||||
getFID(onPerfEntry);
|
||||
getFCP(onPerfEntry);
|
||||
getLCP(onPerfEntry);
|
||||
getTTFB(onPerfEntry);
|
||||
});
|
||||
}
|
||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||
import('web-vitals').then(({getCLS, getFID, getFCP, getLCP, getTTFB}) => {
|
||||
getCLS(onPerfEntry);
|
||||
getFID(onPerfEntry);
|
||||
getFCP(onPerfEntry);
|
||||
getLCP(onPerfEntry);
|
||||
getTTFB(onPerfEntry);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default reportWebVitals;
|
||||
|
|
|
|||
|
|
@ -4,11 +4,14 @@
|
|||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
window.matchMedia = window.matchMedia || function() {
|
||||
window.matchMedia = window.matchMedia || function () {
|
||||
return {
|
||||
matches: false,
|
||||
addListener: function() {},
|
||||
addEventListener: function() {},
|
||||
removeListener: function() {}
|
||||
addListener: function () {
|
||||
},
|
||||
addEventListener: function () {
|
||||
},
|
||||
removeListener: function () {
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -213,7 +213,7 @@ CHANNELS_CONFIG: ChannelsConfig = ChannelsConfig(global_=True)
|
|||
TEST_CHANNELS_CONFIG: ChannelsConfig = None
|
||||
|
||||
|
||||
def get_channels_config():
|
||||
def get_channels_config() -> ChannelsConfig:
|
||||
global TEST_CHANNELS_CONFIG
|
||||
if isinstance(TEST_CHANNELS_CONFIG, ConfigFile):
|
||||
return TEST_CHANNELS_CONFIG
|
||||
|
|
@ -425,6 +425,8 @@ def import_channels_config():
|
|||
save_config = True
|
||||
if not channel.source_id:
|
||||
channel_import_logger.warning(f'Unable to fetch source_id for {channel.url}')
|
||||
else:
|
||||
session.commit()
|
||||
|
||||
channel_import_logger.debug(f'Updated {repr(channel.name)}'
|
||||
f' url={channel.url}'
|
||||
|
|
@ -435,7 +437,7 @@ def import_channels_config():
|
|||
|
||||
if save_config:
|
||||
# Information about the channel was fetched, store it.
|
||||
config.save()
|
||||
save_channels_config()
|
||||
|
||||
with get_db_session(commit=True) as session:
|
||||
channels_by_link = {sanitize_link(i.name): i for i in session.query(Channel)}
|
||||
|
|
|
|||
|
|
@ -258,22 +258,22 @@ def bad_vtt_file(test_directory) -> pathlib.Path:
|
|||
|
||||
@pytest.fixture
|
||||
def example_pdf(test_directory) -> pathlib.Path:
|
||||
destination = test_directory / 'example.pdf'
|
||||
shutil.copy(PROJECT_DIR / 'test/example.pdf', destination)
|
||||
destination = test_directory / 'pdf example.pdf'
|
||||
shutil.copy(PROJECT_DIR / 'test/pdf example.pdf', destination)
|
||||
yield destination
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def example_epub(test_directory) -> pathlib.Path:
|
||||
destination = test_directory / 'example.epub'
|
||||
shutil.copy(PROJECT_DIR / 'test/example.epub', destination)
|
||||
destination = test_directory / 'ebook example.epub'
|
||||
shutil.copy(PROJECT_DIR / 'test/ebook example.epub', destination)
|
||||
yield destination
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def example_mobi(test_directory) -> pathlib.Path:
|
||||
destination = test_directory / 'example.mobi'
|
||||
shutil.copy(PROJECT_DIR / 'test/example.mobi', destination)
|
||||
destination = test_directory / 'ebook example.mobi'
|
||||
shutil.copy(PROJECT_DIR / 'test/ebook example.mobi', destination)
|
||||
yield destination
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ async def test_index(test_session, test_directory, example_epub, example_mobi):
|
|||
assert ebook.creator == 'roland'
|
||||
assert ebook.size == 292579
|
||||
assert ebook.file_group.data == {'creator': 'roland', 'title': 'WROLPi Test Book',
|
||||
'cover_path': test_directory / 'example.jpeg',
|
||||
'ebook_path': test_directory / 'example.epub',
|
||||
'cover_path': test_directory / 'ebook example.jpeg',
|
||||
'ebook_path': test_directory / 'ebook example.epub',
|
||||
}
|
||||
|
||||
assert ebook.file_group.a_text, 'Book title was not indexed'
|
||||
|
|
@ -95,10 +95,11 @@ def test_search(test_session, test_client, example_epub):
|
|||
assert response.json
|
||||
file_group = response.json['file_groups'][0]
|
||||
epub_file = file_group['files'][0]
|
||||
assert epub_file['path'] == 'example.epub' and epub_file['mimetype'] == 'application/epub+zip'
|
||||
assert epub_file['path'] == 'ebook example.epub' and epub_file['mimetype'] == 'application/epub+zip'
|
||||
assert_dict_contains(
|
||||
file_group['data'],
|
||||
{'cover_path': 'example.jpeg', 'ebook_path': 'example.epub', 'title': 'WROLPi Test Book', 'creator': 'roland'},
|
||||
{'cover_path': 'ebook example.jpeg', 'ebook_path': 'ebook example.epub', 'title': 'WROLPi Test Book',
|
||||
'creator': 'roland'},
|
||||
)
|
||||
|
||||
# No Mobi ebook.
|
||||
|
|
@ -122,11 +123,11 @@ async def test_discover_calibre_cover(test_session, test_directory, example_epub
|
|||
ebook: EBook = test_session.query(EBook).one()
|
||||
assert ebook.file_group.title == 'WROLPi Test Book'
|
||||
# The cover in the ebook was extracted.
|
||||
assert ebook.cover_file['path'] == test_directory / 'example.jpeg'
|
||||
assert (test_directory / 'example.jpeg').is_file()
|
||||
assert ebook.cover_file['path'] == test_directory / 'ebook example.jpeg'
|
||||
assert (test_directory / 'ebook example.jpeg').is_file()
|
||||
|
||||
# Delete the extracted cover, use the cover image from Calibre.
|
||||
(test_directory / 'example.jpeg').unlink()
|
||||
(test_directory / 'ebook example.jpeg').unlink()
|
||||
cover_image = test_directory / 'cover.jpg'
|
||||
shutil.move(image_file, cover_image)
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ async def test_pdf_title(test_session, example_pdf):
|
|||
assert pdf.indexed is True
|
||||
assert pdf.title == pdf.a_text == 'WROLPi Test PDF'
|
||||
assert pdf.b_text == 'roland'
|
||||
assert pdf.c_text == 'example pdf'
|
||||
assert pdf.c_text == 'pdf example pdf'
|
||||
assert pdf.d_text and pdf.d_text.startswith('Page one\n') and len(pdf.d_text) == 467
|
||||
assert pdf.data and pdf.data.get('author') == 'roland'
|
||||
|
||||
|
|
@ -40,7 +40,7 @@ async def test_pdf_modeler(test_session, example_pdf):
|
|||
# The author.
|
||||
assert b_text == 'roland'
|
||||
# The parsed file name.
|
||||
assert c_text == 'example pdf'
|
||||
assert c_text == 'pdf example pdf'
|
||||
# All pages are extracted. Text is formatted to fit on vertical screen.
|
||||
assert d_text == 'Page one\n' \
|
||||
'Page two\n' \
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue