@@ -74,10 +80,11 @@ function getEpubModal(path) {
}
-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
{name}
@@ -96,11 +103,12 @@ function getVideoModal(path) {
}
-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
{name}
@@ -132,6 +140,16 @@ export function FilePreviewWrapper({children}) {
};
function setModalContent(content, url, downloadURL) {
+ const openButton = Open;
+ const closeButton = setPreviewFile(null)}>Close;
+ const tagsDisplay = ;
+ let downloadButton;
+ if (downloadURL) {
+ downloadButton =
+ Download
+ ;
+ }
+
setPreviewModal(
{content}
- {downloadURL &&
- window.open(downloadURL)} floated='left'>Download}
- window.open(url)}>Open
- setPreviewFile(null)}>Close
+
+
+
+ {downloadButton}
+ {openButton}
+ {closeButton}
+
+
+ {tagsDisplay}
+
+
+
+
+
+
+ {downloadButton}
+ {tagsDisplay}
+ {openButton}
+ {closeButton}
+
+
+
);
}
@@ -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')) {
diff --git a/app/src/components/Files.js b/app/src/components/Files.js
index fc96af02..70d8a3b7 100644
--- a/app/src/components/Files.js
+++ b/app/src/components/Files.js
@@ -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 = ;
- if (data && data.cover_path) {
- const coverSrc = `/media/${encodeURIComponent(data.cover_path)}`;
- cover = ;
- }
-
const color = mimetypeColor(file.mimetype);
return
-
- {cover}
-
+
- {data ? data.title : file.title}
+ {cardTitleWrapper(data ? data.title : file.title)}
@@ -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 = ;
- if (file.size && file.size < 50000000) {
- // Image is less than 5mb, use it.
- poster = setPreviewFile(file)}
- />
- }
+ const url = `/media/${encodeMediaPath(file.primary_path)}`;
return
- {poster}
+
@@ -114,18 +96,6 @@ function ImageCard({file}) {
}
-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 ;
}
- 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 = ;
- let imageLabel = !_.isEmpty(tags) ? taggedImageLabel : null;
- if (posterFile) {
- const posterUrl = `/media/${encodeURIComponent(posterFile)}`;
- poster = ;
- }
-
return
-
- {poster}
-
+
@@ -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 = ;
if (file.size && file.size < 50000000) {
@@ -214,11 +174,10 @@ function ImageRowCells({file}) {
}
export function EbookRowCells({file}) {
- const {data} = file;
-
let cover = ;
- if (data && data.cover_path) {
- const coverSrc = `/media/${encodeURIComponent(data.cover_path)}`;
+ const posterPath = findPosterPath(file);
+ if (posterPath) {
+ const coverSrc = `/media/${encodeMediaPath(posterPath)}`;
cover = ;
}
@@ -229,6 +188,7 @@ export function EbookRowCells({file}) {
+
{textEllipsis(file.title || file.stem)}
@@ -256,7 +216,8 @@ function FileRow({file}) {
- {textEllipsis(file.title)}
+
+ {textEllipsis(file.title || file.name || file.stem)}
}
@@ -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);
diff --git a/app/src/components/Nav.js b/app/src/components/Nav.js
index 31bf8f59..6929370b 100644
--- a/app/src/components/Nav.js
+++ b/app/src/components/Nav.js
@@ -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},
diff --git a/app/src/components/VideoPlayer.js b/app/src/components/VideoPlayer.js
index ae775191..2036ac66 100644
--- a/app/src/components/VideoPlayer.js
+++ b/app/src/components/VideoPlayer.js
@@ -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
{path}
@@ -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.';
diff --git a/app/src/components/Videos.js b/app/src/components/Videos.js
index b73b1e0c..5a3a8b5f 100644
--- a/app/src/components/Videos.js
+++ b/app/src/components/Videos.js
@@ -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 =
;
- if (poster_url) {
- poster =
;
- }
+ let poster = ;
const color = mimetypeColor(file.mimetype);
return
@@ -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) {
diff --git a/app/src/index.css b/app/src/index.css
index ec2585e8..da92873c 100644
--- a/app/src/index.css
+++ b/app/src/index.css
@@ -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;
}
diff --git a/app/src/logo.svg b/app/src/logo.svg
index 9dfc1c05..6b60c104 100644
--- a/app/src/logo.svg
+++ b/app/src/logo.svg
@@ -1 +1,7 @@
-
\ No newline at end of file
+
diff --git a/app/src/reportWebVitals.js b/app/src/reportWebVitals.js
index 5253d3ad..14ba227b 100644
--- a/app/src/reportWebVitals.js
+++ b/app/src/reportWebVitals.js
@@ -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;
diff --git a/app/src/setupTests.js b/app/src/setupTests.js
index bf39e2ce..020cbb0b 100644
--- a/app/src/setupTests.js
+++ b/app/src/setupTests.js
@@ -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 () {
+ }
};
};
diff --git a/modules/videos/lib.py b/modules/videos/lib.py
index ce4e75c7..c37a0a57 100644
--- a/modules/videos/lib.py
+++ b/modules/videos/lib.py
@@ -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)}
diff --git a/test/example.epub b/test/ebook example.epub
similarity index 100%
rename from test/example.epub
rename to test/ebook example.epub
diff --git a/test/example.mobi b/test/ebook example.mobi
similarity index 100%
rename from test/example.mobi
rename to test/ebook example.mobi
diff --git a/test/example.pdf b/test/pdf example.pdf
similarity index 100%
rename from test/example.pdf
rename to test/pdf example.pdf
diff --git a/wrolpi/conftest.py b/wrolpi/conftest.py
index e8f67458..0288e78e 100644
--- a/wrolpi/conftest.py
+++ b/wrolpi/conftest.py
@@ -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
diff --git a/wrolpi/files/test/test_ebooks.py b/wrolpi/files/test/test_ebooks.py
index d1860da4..e06ac43a 100644
--- a/wrolpi/files/test/test_ebooks.py
+++ b/wrolpi/files/test/test_ebooks.py
@@ -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)
diff --git a/wrolpi/files/test/test_pdfs.py b/wrolpi/files/test/test_pdfs.py
index b89c4fc5..ca4608a5 100644
--- a/wrolpi/files/test/test_pdfs.py
+++ b/wrolpi/files/test/test_pdfs.py
@@ -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' \