diff --git a/main/index.js b/main/index.js index 410e69c7..48850760 100755 --- a/main/index.js +++ b/main/index.js @@ -19,6 +19,39 @@ function isDev() { return process.argv[2] == '--dev'; } +const CACHE_DIR = path.join(app.getPath('userData'), 'video-cache'); + +if (!fs.existsSync(CACHE_DIR)) { + fs.mkdirSync(CACHE_DIR); +} + +// Handle requests to check if a video is cached +ipcMain.handle('is-video-cached', (event, videoUrl) => { + const videoFileName = path.basename(videoUrl); + const localVideoPath = path.join(CACHE_DIR, videoFileName); + return fs.existsSync(localVideoPath) ? localVideoPath : null; +}); + +// Handle requests to download and cache the video +ipcMain.handle('download-and-cache-video', async (event, videoUrl) => { + const videoFileName = path.basename(videoUrl); + const localVideoPath = path.join(CACHE_DIR, videoFileName); + + const net = require('electron').net; + const request = net.request(videoUrl); + + return new Promise((resolve, reject) => { + const fileStream = fs.createWriteStream(localVideoPath); + request.on('response', (response) => { + response.pipe(fileStream); + fileStream.on('finish', () => resolve(localVideoPath)); + }); + + request.on('error', reject); + request.end(); + }); +}); + async function setPermissions(chromePath) { try { fs.chmodSync(chromePath, '755'); diff --git a/renderer/environment.js b/renderer/environment.js index f855a8e7..50e434a4 100644 --- a/renderer/environment.js +++ b/renderer/environment.js @@ -10,6 +10,8 @@ export const environment = { GITEA_SERVER: 'https://git.door43.org', GITEA_TOKEN: 'Gitea AG Testing', GITEA_API_ENDPOINT: 'https://git.door43.org/api/v1', + GITHUB_SERVER: 'https://github.com', + GITHUB_API_ENDPOINT: 'https://api.github.com', uuidToken: '6223f833-3e59-429c-bec9-16910442b599', SYNC_BACKUP_COUNT: 5, AG_MINIMUM_BURRITO_VERSION: '0.3.0', diff --git a/renderer/src/components/EditorPage/Navigation/reference/SelectBook.js b/renderer/src/components/EditorPage/Navigation/reference/SelectBook.js index cb28054e..e4642727 100644 --- a/renderer/src/components/EditorPage/Navigation/reference/SelectBook.js +++ b/renderer/src/components/EditorPage/Navigation/reference/SelectBook.js @@ -38,7 +38,7 @@ export default function SelectBook({ function bookSelect(e, bookId) { e.preventDefault(); onChangeBook(bookId, selectedBooks[0]); - setBook(bookId); + setBook && setBook(bookId); if (multiSelectBook === false) { selectBook(); } } diff --git a/renderer/src/components/EditorPage/Reference/TranslationHelps.js b/renderer/src/components/EditorPage/Reference/TranslationHelps.js index eb4111cb..fa753ab4 100644 --- a/renderer/src/components/EditorPage/Reference/TranslationHelps.js +++ b/renderer/src/components/EditorPage/Reference/TranslationHelps.js @@ -1,10 +1,14 @@ -import React, { useContext } from 'react'; +import React, { useContext, useState, useEffect } from 'react'; +import * as localforage from 'localforage'; import PropTypes from 'prop-types'; import { useTranslation } from 'react-i18next'; import { ReferenceContext } from '@/components/context/ReferenceContext'; +import { debug } from '../../../logger'; import TranslationHelpsCard from './TranslationHelpsCard'; +import TranslationHelpsMultimediaCard from './TranslationHelpsMultimediaCard'; import ObsTnCard from './OBS/ObsTn'; import ObsTwlCard from './OBS/ObsTwlCard'; +import packageInfo from '../../../../../package.json'; const TranslationHelps = ({ selectedResource, languageId, refName, bookId, chapter, verse, owner, story, offlineResource, font, fontSize, @@ -17,16 +21,58 @@ const TranslationHelps = ({ } = useContext(ReferenceContext); const { t } = useTranslation(); + /** + * Function to search a directory for a file containing a specific name part. + * @param {string} directoryPath - The path of the directory to search in. + * @param {string} partialName - The partial name to search for in the file names. + * @returns {string|null} - The full name of the matched file, or null if not found. + */ + const findFileByPartialName = (fsInstance, directoryPath, partialName) => fsInstance.readdirSync(directoryPath).find((file) => file.includes(partialName)) || null; + const translationQuestionsPath = `${(chapter < 10) ? (`0${ chapter}`) : chapter}/${(verse < 10) ? (`0${ verse}`) : verse}.md`; const filePathTa = `${taNavigationPath?.path}/01.md`; + const [resourceLinkPath, setResourceLinkPath] = useState(''); + const [imagesPath, setImagesPath] = useState(''); + + useEffect(() => { + async function getLinkedFolderPath() { + const fs = window.require('fs'); + const path = window.require('path'); + try { + const newpath = localStorage.getItem('userPath'); + const userProfile = await localforage.getItem('userProfile'); + const resourceDirPath = path.join(newpath, packageInfo.name, 'users', userProfile?.username, 'resources'); + const pathToIngredients = path.resolve(resourceDirPath, offlineResource.data.projectDir, 'ingredients'); + if (pathToIngredients) { + const pathRelationFile = path.resolve(pathToIngredients, 'relation.txt'); + if (fs.existsSync(pathRelationFile)) { + setImagesPath(pathToIngredients); + const relationFileContent = fs.readFileSync(pathRelationFile, 'utf8'); + const fileName = findFileByPartialName(fs, path.resolve(resourceDirPath), relationFileContent.trim()); + setResourceLinkPath(path.resolve(resourceDirPath, fileName, 'ingredients')); + } else { + setImagesPath(''); + setResourceLinkPath(pathToIngredients); + debug('TranslationHelps.js', `pathRelationFile : ${pathRelationFile} - Not found!`); + } + } + } catch (e) { + debug('TranslationHelps.js', `Error : ${e}`); + } + } + + getLinkedFolderPath(); + }, [selectedResource, offlineResource]); + return ( <> {(() => { switch (selectedResource) { case 'tn': + case 'x-bcvnotes': return ( ); + case 'tir': + return ( + + ); // case 'twl': // return ( // { if (currentTnTab === 1) { @@ -73,6 +84,7 @@ export default function TranslationHelpsCard({ try { setOfflineMarkdown(''); setOfflineItems(''); + let isBurrito = false; localForage.getItem('userProfile').then(async (user) => { logger.debug('TranslationHelpsCard.js', `reading offline helps ${offlineResource.data?.projectDir}`); const fs = window.require('fs'); @@ -80,19 +92,48 @@ export default function TranslationHelpsCard({ const newpath = localStorage.getItem('userPath'); const currentUser = user?.username; const folder = path.join(newpath, packageInfo.name, 'users', `${currentUser}`, 'resources'); - const projectName = `${offlineResource?.data?.value?.meta?.name}_${offlineResource?.data?.value?.meta?.owner}_${offlineResource?.data?.value?.meta?.release?.tag_name}`; + let projectName = `${offlineResource?.data?.value?.meta?.name}_${offlineResource?.data?.value?.meta?.owner}_${offlineResource?.data?.value?.meta?.release?.tag_name}`; + if (!offlineResource?.data?.value?.meta?.name) { + isBurrito = true; + projectName = offlineResource?.data?.projectDir; + } + // projectName = `${offlineResource?.data?.value?.meta?.name}_${offlineResource?.data?.value?.meta?.owner}_${offlineResource?.data?.value?.meta?.release?.tag_name}`; // switch resources switch (resourceId) { case 'tn': + case 'x-bcvnotes': + // console.log("yep", folder, 'and projectName ==', projectName); if (fs.existsSync(path.join(folder, projectName))) { // eslint-disable-next-line array-callback-return - const currentFile = offlineResource?.data?.value?.projects.filter((item) => { - if (item?.identifier.toLowerCase() === projectId.toLowerCase()) { - return item; + let currentFile; + if (isBurrito) { + const asArray = Object.entries(offlineResource?.data?.value?.ingredients); + // currentFile = asArray.filter(([key, value]) => { + // if (key.toLocaleLowerCase().indexOf(projectId.toLowerCase()) !== -1) { + // console.log("key ==", key); + // console.log("value ==", value); + // return value; + // } + // return []; + // })[0]; + // eslint-disable-next-line + for (const [key, value] of asArray) { + if (key.toLocaleLowerCase().indexOf(projectId.toLowerCase()) !== -1) { + currentFile = key; + break; + } } - }); + } else { + currentFile = offlineResource?.data?.value?.projects.filter((item) => { + if (item?.identifier.toLowerCase() === projectId.toLowerCase()) { + return item; + } + return null; + }); + } if (currentFile?.length > 0) { - const filecontent = await fs.readFileSync(path.join(folder, projectName, currentFile[0].path), 'utf8'); + // const filecontent = await fs.readFileSync(path.join(folder, projectName, currentFile[0].path), 'utf8'); + const filecontent = await fs.readFileSync(path.join(folder, projectName, isBurrito ? currentFile : currentFile[0].path), 'utf8'); // convert tsv to json const headerArr = filecontent.split('\n')[0].split('\t'); let noteName; @@ -111,21 +152,21 @@ export default function TranslationHelpsCard({ } const json = filecontent.split('\n') - .map((file) => { + .map((line) => { if (bvcType) { - const [Book, Chapter, Verse, ID, SupportReference, OrigQuote, Occurrence, GLQuote, OccurrenceNote] = file.split('\t'); + const [Book, Chapter, Verse, ID, SupportReference, OrigQuote, Occurrence, GLQuote, OccurrenceNote] = line.split('\t'); return { Book, Chapter, Verse, ID, SupportReference, OrigQuote, Occurrence, GLQuote, OccurrenceNote, }; } const Book = projectId; - const [ref, ID] = file.split('\t'); + const [ref, ID] = line.split('\t'); const Chapter = ref.split(':')[0]; const Verse = ref.split(':')[1]; return { - Book, Chapter, Verse, ID, [noteName]: file.split('\t')[indexOfNote], + Book, Chapter, Verse, ID, [noteName]: line.split('\t')[indexOfNote], }; - }).filter((data) => data.Chapter === currentChapterVerse.chapter && data.Verse === currentChapterVerse.verse); + }).filter((data) => data.Chapter === `${currentChapterVerse.chapter }` && data.Verse === `${currentChapterVerse.verse }`); setOfflineItemsDisable(false); setOfflineItems(json); } @@ -200,7 +241,7 @@ export default function TranslationHelpsCard({ setOfflineItemsDisable(true); setOfflineMarkdown(filecontent); } else { - setOfflineMarkdown({ error: true, data: 'No Content Avaialble' }); + setOfflineMarkdown({ error: true, data: 'No Content Available' }); } } }); @@ -247,7 +288,7 @@ export default function TranslationHelpsCard({ items = !offlineItemsDisable && offlineResource?.offline ? offlineItems : items; markdown = offlineResource?.offline ? offlineMarkdown : markdown; - if (resourceId === 'tn' && items) { + if ((resourceId === 'tn' || resourceId === 'x-bcvnotes') && items) { if (items[0]?.Note) { items[0].Note = (items[0].Note).replace(/(
|\\n)/gm, '\n'); } @@ -258,7 +299,7 @@ export default function TranslationHelpsCard({ return ( <> - {resourceId === 'tn' && ()} + {(resourceId === 'tn' || resourceId === 'x-bcvnotes') && ()} {(markdown || items) ? ( { + const [videoSrc, setVideoSrc] = useState(videoUrl); // Default to remote video URL + + useEffect(() => { + const loadVideo = async () => { + // Check if video is already cached + const cachedVideoPath = await global.ipcRenderer.invoke('is-video-cached', videoUrl); + if (cachedVideoPath) { + setVideoSrc(`file://${cachedVideoPath}`); // Use cached video path + } else { + // If not cached, download and cache the video + const downloadedPath = await global.ipcRenderer.invoke('download-and-cache-video', videoUrl); + setVideoSrc(`file://${downloadedPath}`); // Set downloaded video path + } + }; + + loadVideo(); + }, [videoUrl]); + + return ( +