From 5802aae878cb83778c4ab088f0fd3e8e1f79a561 Mon Sep 17 00:00:00 2001 From: danielc-n Date: Wed, 10 Apr 2024 17:31:04 +0200 Subject: [PATCH 01/20] changed place of the 'nprogress' by few px --- styles/nprogress.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/styles/nprogress.css b/styles/nprogress.css index ec1b9bee..4df7127c 100644 --- a/styles/nprogress.css +++ b/styles/nprogress.css @@ -34,8 +34,8 @@ display: block; position: fixed; z-index: 1031; - top: 15px; - right: 15px; + top: 23px; + right: 10%; } #nprogress .spinner-icon { From 30dc501b3ad7395ec1c1a1c68d4e21dabfc1ac68 Mon Sep 17 00:00:00 2001 From: danielc-n Date: Mon, 9 Sep 2024 16:12:40 +0200 Subject: [PATCH 02/20] trying a configuration to increase 'maxConcurrentTasks' --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index 70862d07..58b35a1b 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,9 @@ "buildResources": "resources", "output": "dist" }, + "node": { + "maxConcurrentTasks": 4 + }, "files": [ "main", "renderer/out", From ad4f4eaf48cb8336a093591470d06e958a24bee3 Mon Sep 17 00:00:00 2001 From: danielc-n Date: Mon, 9 Sep 2024 16:54:46 +0200 Subject: [PATCH 03/20] removed @mui/icons-react from deps and let it in devDeps --- package.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/package.json b/package.json index 58b35a1b..70862d07 100644 --- a/package.json +++ b/package.json @@ -43,9 +43,6 @@ "buildResources": "resources", "output": "dist" }, - "node": { - "maxConcurrentTasks": 4 - }, "files": [ "main", "renderer/out", From c4925747df1b74b9a24b4f51f41ddc6d275c1814 Mon Sep 17 00:00:00 2001 From: danielc-n Date: Tue, 15 Oct 2024 15:30:02 +0200 Subject: [PATCH 04/20] added the full feature with images clickable --- renderer/environment.js | 2 + .../EditorPage/Reference/TranslationHelps.js | 65 ++++++++- .../Reference/TranslationHelpsCard.js | 84 ++++++++---- .../Reference/TranslationHelpsImageCard.js | 127 ++++++++++++++++++ .../src/components/Resources/ListResources.js | 106 +++++++++------ .../ResourceUtils/DownloadCreateSBforHelps.js | 69 +++++++--- .../Resources/ResourceUtils/RemoveResource.js | 26 +++- .../components/Resources/ResourcesPopUp.js | 72 ++++++++-- .../components/Resources/ResourcesSideBar.js | 7 + .../Resources/useFetchTranslationResource.js | 75 +++++++++-- .../Resources/useGetOnlineOfflineResource.js | 1 + .../Resources/useHandleChangeQuery.js | 2 +- renderer/src/translations/en.js | 1 + renderer/src/translations/id.js | 1 + styles/globals.css | 103 ++++++++++++++ 15 files changed, 634 insertions(+), 107 deletions(-) create mode 100644 renderer/src/components/EditorPage/Reference/TranslationHelpsImageCard.js 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/Reference/TranslationHelps.js b/renderer/src/components/EditorPage/Reference/TranslationHelps.js index eb4111cb..e7169360 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 TranslationHelpsImageCard from './TranslationHelpsImageCard'; 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,56 @@ 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'); + setImagesPath(pathToIngredients); + if (pathToIngredients) { + const pathRelationFile = path.resolve(pathToIngredients, 'relation.txt'); + if (fs.existsSync(pathRelationFile)) { + const relationFileContent = fs.readFileSync(pathRelationFile, 'utf8'); + const fileName = findFileByPartialName(fs, path.resolve(resourceDirPath), relationFileContent); + setResourceLinkPath(path.resolve(resourceDirPath, fileName, 'ingredients')); + } else { + debug('TranslationHelps.js', `pathRelationFile : ${pathRelationFile} - Not found!`); + } + } + } catch (e) { + debug('TranslationHelps.js', `Error : ${e}`); + } + } + + getLinkedFolderPath(); + }, [imagesPath]); + 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,39 @@ 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) { + return value; + } + return []; + })[0]; + } 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[0] : currentFile[0].path), 'utf8'); // convert tsv to json const headerArr = filecontent.split('\n')[0].split('\t'); let noteName; @@ -111,21 +143,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); } @@ -247,7 +279,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 +290,7 @@ export default function TranslationHelpsCard({ return ( <> - {resourceId === 'tn' && ()} + {(resourceId === 'tn' || resourceId === 'x-bcvnotes') && ()} {(markdown || items) ? ( `file://${path}`; + + /** + * Function to get image paths from a TSV file based on book code, chapter, and verse. + * + * @returns {Array} - A list of image paths for the specified reference. + */ + const getImagePathsFromTSV = () => { + const fs = window.require('fs'); + const path = require('path'); + const filePath = path.join(folderPath, `${projectId}.tsv`); + const metadataJson = JSON.parse(fs.readFileSync(path.join(linkedFolderPath, '..', 'metadata.json'))); + + // Check if the file exists + if (!fs.existsSync(filePath)) { + return []; + } + + const reference = `${chapter}:${verse}`; + const finalImagePaths = []; + + // Read the TSV file + const fileContent = fs.readFileSync(filePath, 'utf-8'); + + // Split the content into lines + const lines = fileContent.trim().split('\n'); + + // Loop through each line, starting from 1 to skip the header + let columns; + let realPath; + let dashedName; + let dashedNameSplited; + let localizedNameTmp; + for (let i = 1; i < lines.length; i++) { + columns = lines[i].split('\t'); + + // Check if the reference matches + if (columns[0] === reference) { + // Add the last column (image path) to the list + dashedName = columns[columns.length - 1].split('images/')[1]; + dashedNameSplited = dashedName.split('/')[1]; + localizedNameTmp = metadataJson.localizedNames[dashedNameSplited]; + realPath = convertToFileUrl(path.join(linkedFolderPath, `${dashedName }.jpg`)); + if (localizedNameTmp) { + localizedNameTmp = localizedNameTmp.short.en; + finalImagePaths.push([localizedNameTmp, realPath]); + } else { + finalImagePaths.push([dashedNameSplited, realPath]); + } + } + } + + return finalImagePaths; + }; + + useEffect(() => { + if (linkedFolderPath && linkedFolderPath !== '') { + setImagePaths(getImagePathsFromTSV()); + } + }, [chapter, verse, projectId]); + + const [zoomedImage, setZoomedImage] = useState(null); + + const closeZoom = () => { + setZoomedImage(null); + }; + + const handleImageClick = (imageSrc, imageTitle) => { + setZoomedImage({ src: imageSrc, title: imageTitle }); + }; + + return ( +
+
+ {imagePaths.length > 0 && imagePaths + .sort((a, b) => a[0].localeCompare(b[0])) + .map((imagePath, index) => ( +
+
{imagePath[0]}
+ {`Image handleImageClick(imagePath[1], imagePath[0])} + /> +
+ ))} + {imagePaths.length === 0 + && 'No resources image for this book'} +
+ + {zoomedImage && ( +
+
+ X +
{zoomedImage.title}
+ Zoomed +
+
+ )} +
+ ); +} diff --git a/renderer/src/components/Resources/ListResources.js b/renderer/src/components/Resources/ListResources.js index f4634ee9..aadcde4d 100644 --- a/renderer/src/components/Resources/ListResources.js +++ b/renderer/src/components/Resources/ListResources.js @@ -20,6 +20,7 @@ export const ListResources = ({ loading, setLoading, filteredResources, + filteredReposResourcelinks, downloading, selectResource, handleRowSelect, @@ -33,8 +34,10 @@ export const ListResources = ({ subMenuItems, setSubMenuItems, setCurrentFullResources, + setFilteredReposResourcelinks, setFilteredResources, setfilteredBibleObsAudio, + endPoint, }) => { const { states: { @@ -56,6 +59,7 @@ export const ListResources = ({ const { t } = useTranslation(); const [translationWordList, settranslationWordList] = useState([]); const [translationNote, setTranslationNote] = useState([]); + const [translationImageResources, setTranslationImageResources] = useState([]); const [juxtalinear, setJuxtalinear] = useState([]); const [translationQuestion, setTranslationQuestion] = useState([]); // const [translationWord, settranslationWord] = useState([]); @@ -69,14 +73,14 @@ export const ListResources = ({ try { logger.debug('ResourcesPopUp.js', 'Helps Download started'); setCurrentDownloading(reference); - await DownloadCreateSBforHelps(reference?.responseData, setDownloading, false, offlineResource); + await DownloadCreateSBforHelps(reference?.responseData, setDownloading, false, offlineResource, endPoint, filteredReposResourcelinks); setCurrentDownloading(null); setOpenSnackBar(true); setError('success'); setRenderApp(true); setSnackText('Resource Download Finished'); } catch (err) { - logger.debug('ResourcesPopUp.js', 'Error Downlaod ', err); + logger.debug('ResourcesPopUp.js', 'Error Download ', err); setOpenSnackBar(true); setError('failure'); setSnackText(err?.message); @@ -101,7 +105,11 @@ export const ListResources = ({ await fetchTranslationResource('Juxtalinear', setJuxtalinear, selectResource, selectedPreProd, snackBarAction); break; case 'tn': - await fetchTranslationResource('TSV Translation Notes', setTranslationNote, selectResource, selectedPreProd, snackBarAction); + case 'x-bcvnotes': + await fetchTranslationResource('TSV Translation Notes', setTranslationNote, selectResource, selectedPreProd, snackBarAction, endPoint); + break; + case 'tir': + await fetchTranslationResource('TSV Translation Image Resources', setTranslationImageResources, selectResource, selectedPreProd, snackBarAction, endPoint, setFilteredReposResourcelinks); break; // case 'tw': // await fetchTranslationResource('Translation Words', settranslationWord, selectResource, selectedPreProd, snackBarAction); @@ -130,13 +138,15 @@ export const ListResources = ({ setLoading(false); })(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectResource, selectedPreProd]); + }, [selectResource, selectedPreProd, endPoint]); useEffect(() => { const getCurrentOnlineOfflineHelpsResources = (selectResource) => { const resources = [ { id: 'jxl', title: 'Juxtalinear', resource: juxtalinear }, { id: 'tn', title: t('label-resource-tn'), resource: translationNote }, + { id: 'x-bcvnotes', title: t('label-resource-tn'), resource: translationNote }, + { id: 'tir', title: t('label-resource-tir'), resource: translationImageResources }, { id: 'twlm', title: t('label-resource-twl'), resource: translationWordList }, // { id: 'tw', title: t('label-resource-twlm'), resource: translationWord }, { id: 'tq', title: t('label-resource-tq'), resource: translationQuestion }, @@ -145,7 +155,10 @@ export const ListResources = ({ { id: 'obs-tq', title: t('label-resource-obs-tq'), resource: obsTranslationQuestion }, { id: 'obs-twlm', title: t('label-resource-obs-twl'), resource: obsTranslationWordList }]; const reference = resources.find((r) => r.id === selectResource); - const offlineResource = subMenuItems ? subMenuItems?.filter((item) => item?.value?.agOffline && item?.value?.dublin_core?.identifier === selectResource) : []; + // filter for a resource OR an x-bcvnotes burrito + // const offlineResource = subMenuItems ? subMenuItems.filter((item) => (item?.value?.type?.flavorType?.flavor?.name === 'x-bcvnotes' || item?.value?.type?.flavorType?.flavor?.name === 'x-resourcelinks') || (item?.value?.agOffline && item?.value?.dublin_core?.identifier === selectResource)) : []; + const offlineResource = subMenuItems ? subMenuItems.filter((item) => (item?.value?.agOffline && item?.value?.dublin_core?.identifier === selectResource) || (item?.value?.type?.flavorType?.flavor?.name === 'x-bcvnotes' && selectResource === 'tn') || (item?.value?.type?.flavorType?.flavor?.name === 'x-imagedict' && selectResource === 'tir')) : []; + return { reference, offlineResource }; }; const data = getCurrentOnlineOfflineHelpsResources(selectResource); @@ -188,26 +201,27 @@ export const ListResources = ({ )} {/* offline resources body */} {filteredResources?.offlineResource?.length > 0 && filteredResources?.offlineResource?.map((resource) => ( - +
handleRowSelect( e, - resource?.value?.meta?.language, - `${resource?.value?.meta?.subject} ${resource?.value?.meta?.language_title}`, - resource?.value?.meta?.owner, - resource?.value?.meta?.subject, - '', + resource?.value?.meta?.language ?? resource?.value?.languages[0].name.en, + resource.projectDir ?? `${resource?.value?.meta?.subject} ${resource?.value?.meta?.language_title}`, + resource?.value?.meta?.owner ?? '', + resource?.value?.type?.flavorType?.name ?? resource?.value?.meta?.subject, + resource?.type ?? '', resource, )} role="button" tabIndex="0" aria-label="upload" > - {resource?.value?.meta?.language_title ?? resource?.value?.meta?.name } + {resource?.value?.meta?.language_title ?? resource?.value?.meta?.name ?? resource?.value.identification.name.en} + {/* {resource?.value.identification.name.en } */} - {resource?.value?.meta?.owner} + {resource?.value?.meta?.owner ?? resource?.identification?.abbreviation?.en ?? resource?.projectDir} {resource?.value?.localUploadedHelp && ( @@ -221,18 +235,18 @@ export const ListResources = ({ // className="focus:outline-none" onClick={(e) => handleRowSelect( e, - resource?.value?.meta?.language, - `${resource?.value?.meta?.subject} ${resource?.value?.meta?.language_title}`, - resource?.value?.meta?.owner, - resource?.value?.meta?.subject, - '', + resource?.value?.meta?.language ?? resource?.value?.languages[0].name.en, + resource.projectDir ?? `${resource?.value?.meta?.subject} ${resource?.value?.meta?.language_title}`, + resource?.value?.meta?.owner ?? '', + resource?.value?.type?.flavorType?.name ?? resource?.value?.meta?.subject, + resource?.type ?? '', resource, )} role="button" aria-label="language" tabIndex="0" > - {resource?.value?.meta?.language} + {resource?.value?.meta?.language ?? resource?.value?.languages[0].tag}
@@ -240,18 +254,18 @@ export const ListResources = ({ className="focus:outline-none" onClick={(e) => handleRowSelect( e, - resource?.value?.meta?.language, - `${resource?.value?.meta?.subject} ${resource?.value?.meta?.language_title}`, - resource?.value?.meta?.owner, - resource?.value?.meta?.subject, - '', + resource?.value?.meta?.language ?? resource?.value?.languages[0].name.en, + resource.projectDir ?? `${resource?.value?.meta?.subject} ${resource?.value?.meta?.language_title}`, + resource?.value?.meta?.owner ?? '', + resource?.value?.type?.flavorType?.name ?? resource?.value?.meta?.subject, + resource?.type ?? '', resource, )} role="button" aria-label="version" tabIndex="0" > - {resource?.value?.meta && !resource?.value?.localUploadedHelp && `${(resource.value.meta.released).split('T')[0]}`} + {resource?.value?.meta && !resource?.value?.localUploadedHelp && `${(resource?.value?.meta?.dateCreated ?? resource.value.meta.released).split('T')[0]}`} @@ -259,18 +273,18 @@ export const ListResources = ({ className="focus:outline-none" onClick={(e) => handleRowSelect( e, - resource?.value?.meta?.language, - `${resource?.value?.meta?.subject} ${resource?.value?.meta?.language_title}`, - resource?.value?.meta?.owner, - resource?.value?.meta?.subject, - '', + resource?.value?.meta?.language ?? resource?.value?.languages[0].name.en, + resource.projectDir ?? `${resource?.value?.meta?.subject} ${resource?.value?.meta?.language_title}`, + resource?.value?.meta?.owner ?? '', + resource?.value?.type?.flavorType?.name ?? resource?.value?.meta?.subject, + resource?.type ?? '', resource, )} role="button" aria-label="tag" tabIndex="0" > - {resource?.value?.meta?.release.tag_name} + {resource?.value?.meta?.release?.tag_name ?? resource?.value?.meta?.version} @@ -301,11 +315,16 @@ export const ListResources = ({ {/* online resources section */} {filteredResources?.onlineResource?.resource?.length > 0 && filteredResources?.onlineResource?.resource?.map((notes) => ( - + (selectResource !== 'tir' ? handleRowSelect(e, notes.language, `${filteredResources?.onlineResource?.title} ${notes.name}`, notes.owner, '') : handleDownloadHelpsResources(e, notes, filteredResources?.offlineResource))} + role="button" + >
handleRowSelect(e, notes.language, `${filteredResources?.onlineResource?.title} ${notes.name}`, notes.owner, '')} role="button" tabIndex="0" > @@ -318,7 +337,7 @@ export const ListResources = ({
handleRowSelect(e, notes.language, `${filteredResources?.onlineResource?.title} ${notes.name}`, notes.owner, '')} + // onClick={(e) => selectResource !== 'tir' ? handleRowSelect(e, notes.language, `${filteredResources?.onlineResource?.title} ${notes.name}`, notes.owner, '') : handleDownloadHelpsResources(e, notes, filteredResources?.offlineResource)} role="button" tabIndex="0" > @@ -328,31 +347,38 @@ export const ListResources = ({
handleRowSelect(e, notes.language, `${filteredResources?.onlineResource?.title} ${notes.name}`, notes.owner, '')} + // onClick={(e) => selectResource !== 'tir' ? handleRowSelect(e, notes.language, `${filteredResources?.onlineResource?.title} ${notes.name}`, notes.owner, '') : handleDownloadHelpsResources(e, notes, filteredResources?.offlineResource)} role="button" tabIndex="0" > - {notes?.responseData && `${(notes.responseData.released).split('T')[0]}`} + {notes?.responseData && notes.responseData.released ? `${(notes.responseData.released).split('T')[0]}` : `${(notes.responseData.pushed_at).split('T')[0]}`}
handleRowSelect(e, notes.language, `${filteredResources?.onlineResource?.title} ${notes.name}`, notes.owner, '')} + // onClick={(e) => selectResource !== 'tir' ? handleRowSelect(e, notes.language, `${filteredResources?.onlineResource?.title} ${notes.name}`, notes.owner, '') : handleDownloadHelpsResources(e, notes, filteredResources?.offlineResource)} role="button" tabIndex="0" > - {notes?.responseData?.release.tag_name} + {notes?.responseData?.release?.tag_name ?? notes?.responseData?.id}
- + {/* eslint-disable-next-line */} + { + e.stopPropagation(); + handleDownloadHelpsResources(e, notes, filteredResources?.offlineResource); + }} + > {(filteredResources?.onlineResource?.id !== 'twlm' && filteredResources?.onlineResource?.id !== 'obs-twlm') && (
handleDownloadHelpsResources(e, notes, filteredResources?.offlineResource)} + > {downloading && currentDownloading?.responseData?.id === notes?.responseData?.id ? (
diff --git a/renderer/src/components/Resources/ResourceUtils/DownloadCreateSBforHelps.js b/renderer/src/components/Resources/ResourceUtils/DownloadCreateSBforHelps.js index 46cb20cb..5e735a6f 100644 --- a/renderer/src/components/Resources/ResourceUtils/DownloadCreateSBforHelps.js +++ b/renderer/src/components/Resources/ResourceUtils/DownloadCreateSBforHelps.js @@ -11,7 +11,7 @@ import { const JSZip = require('jszip'); -const DownloadCreateSBforHelps = async (projectResource, setLoading, update = false, offlineResource = false) => { +const DownloadCreateSBforHelps = async (projectResource, setLoading, update = false, offlineResource = false, endPoint = 'gitea', filteredReposResourcelinks = []) => { if (isElectron()) { try { logger.debug('DownloadCreateSBforHelps.js', 'Download Started'); @@ -27,10 +27,19 @@ const DownloadCreateSBforHelps = async (projectResource, setLoading, update = fa // const id = uuidv5(key, environment.uuidToken); // check for existing resources const existingResource = fs.readdirSync(folder, { withFileTypes: true }); - const downloadProjectName = `${projectResource?.name}_${projectResource?.owner}_${projectResource?.release?.tag_name}`; + const projectName = projectResource?.name; + const projectOwner = endPoint === 'gitea' ? projectResource?.owner : projectResource?.owner?.login; + let downloadProjectName = `${projectName}_`; + if (endPoint === 'gitea') { + downloadProjectName += `${projectOwner}_${projectResource?.release?.tag_name}`; + } else { + downloadProjectName += `${projectOwner}_${projectResource?.id}`; + } existingResource?.forEach((element) => { if (downloadProjectName === element.name) { - throw new Error('Resource Already Exist'); + setLoading(false); + + // throw new Error('Resource Already Exist'); } }); @@ -38,21 +47,25 @@ const DownloadCreateSBforHelps = async (projectResource, setLoading, update = fa if (!update && offlineResource) { // eslint-disable-next-line array-callback-return const resourceExist = offlineResource.filter((offline) => { - if (offline?.projectDir === `${projectResource?.name}_${projectResource?.owner}_${projectResource?.release?.tag_name}`) { + // if (offline?.projectDir === `${projectName}_${projectOwner}_${projectResource?.release?.tag_name}`) { + if (offline?.projectDir === downloadProjectName) { return offline; } }); if (resourceExist.length > 0) { - throw new Error('Resource Already Exist'); + setLoading(false); + return; + // throw new Error('Resource Already Exist'); + // throw new Error('Resource Already Exist'); // eslint-disable-next-line no-throw-literal // throw 'Resource Already Exist'; } } - // eslint-disable-next-line no-async-promise-executor - // return new Promise(async (resolve) => { + if (endPoint === 'github') { projectResource.zipball_url = `${projectResource.svn_url }/archive/refs/heads/main.zip`; } // const json = {}; // download and unzip the content + // eslint-disable-next-line no-async-promise-executor await fetch(projectResource?.zipball_url) .then((res) => res.arrayBuffer()) .then(async (blob) => { @@ -61,12 +74,12 @@ const DownloadCreateSBforHelps = async (projectResource, setLoading, update = fa fs.mkdirSync(folder, { recursive: true }); } // wririntg zip to local - await fs.writeFileSync(path.join(folder, `${projectResource?.name}.zip`), Buffer.from(blob)); + await fs.writeFileSync(path.join(folder, `${projectName}.zip`), Buffer.from(blob)); logger.debug('DownloadCreateSBforHelps.js', 'In resource download - downloading zip content completed '); // extract zip logger.debug('DownloadCreateSBforHelps.js', 'In resource download - Unzip downloaded resource'); - const filecontent = await fs.readFileSync(path.join(folder, `${projectResource?.name}.zip`)); + const filecontent = await fs.readFileSync(path.join(folder, `${projectName}.zip`)); const result = await JSZip.loadAsync(filecontent); const keys = Object.keys(result.files); @@ -89,7 +102,7 @@ const DownloadCreateSBforHelps = async (projectResource, setLoading, update = fa data.agOffline = true; data.meta = projectResource; data.lastUpdatedAg = moment().format(); - await fs.writeFileSync(path.join(folder, projectResource?.name, 'metadata.json'), JSON.stringify(data)); + await fs.writeFileSync(path.join(folder, projectName, 'metadata.json'), JSON.stringify(data)); }).catch((err) => { logger.debug('DownloadCreateSBforHelps.js', 'failed to save yml metadata.json : ', err); }); @@ -97,24 +110,46 @@ const DownloadCreateSBforHelps = async (projectResource, setLoading, update = fa // finally remove zip and rename base folder to projectname_id logger.debug('DownloadCreateSBforHelps.js', 'deleting zip file - rename project with project + id in scribe format'); if (fs.existsSync(folder)) { - fs.renameSync(path.join(folder, projectResource?.name), path.join(folder, `${projectResource?.name}_${projectResource?.owner}_${projectResource?.release?.tag_name}`)); - fs.unlinkSync(path.join(folder, `${projectResource?.name}.zip`), (err) => { + const prjMain = endPoint === 'github' ? `${projectName }-main` : projectName; + fs.renameSync(path.join(folder, prjMain), path.join(folder, downloadProjectName)); + fs.unlinkSync(path.join(folder, `${projectName}.zip`), (err) => { if (err) { logger.debug('DownloadCreateSBforHelps.js', 'error in deleting zip'); - throw new Error(`Removing Resource Zip Failed : ${projectResource?.name}.zip`); + throw new Error(`Removing Resource Zip Failed : ${projectName}.zip`); } }); if (update && update?.status) { // if updation delete old resource try { - fs.rmSync(path.join(folder, `${projectResource?.name}_${projectResource?.owner}_${update?.prevVersion}`), { recursive: true }); + fs.rmSync(path.join(folder, `${projectName}_${projectOwner}_${update?.prevVersion}`), { recursive: true }); update && update?.setIsOpen(false); } catch (err) { logger.debug('DownloadCreateSBforHelps.js', 'error in deleting prev resource'); setLoading(false); - throw new Error(`Removing Previous Resource Failed : ${projectResource?.name}_${projectResource?.owner}_${update?.prevVersion}`); + throw new Error(`Removing Previous Resource Failed : ${projectName}_${projectOwner}_${update?.prevVersion}`); + } + } + } + + const pathRelationFile = path.join(folder, downloadProjectName, 'ingredients', 'relation.txt'); + if (fs.existsSync(pathRelationFile) && filteredReposResourcelinks.length > 0) { + const relationFileContent = await fs.readFileSync(pathRelationFile, 'utf8'); + const nameResourceLinked = relationFileContent.replace('\n', '').trim(); + let resourceLinkBurrito = ''; + for (const resource of filteredReposResourcelinks) { + if (resource.name === nameResourceLinked) { + resourceLinkBurrito = resource; } } + + if (resourceLinkBurrito !== '') { + logger.debug('DownloadCreateSBforHelps.js', `Linked resource found for ${downloadProjectName} as ${resourceLinkBurrito}`); + DownloadCreateSBforHelps(resourceLinkBurrito.responseData, setLoading, update, offlineResource, 'github'); + } else { + logger.debug('DownloadCreateSBforHelps.js', `No linked resource found for ${downloadProjectName}`); + } + } else { + logger.debug('DownloadCreateSBforHelps.js', `Nope! ${downloadProjectName}\n${filteredReposResourcelinks.length}\nfs.existsSync(pathRelationFile) == ${fs.existsSync(pathRelationFile)}\npathRelationFile == ${pathRelationFile}`); } }); logger.debug('DownloadCreateSBforHelps.js', 'download completed'); @@ -134,7 +169,9 @@ const DownloadCreateSBforHelps = async (projectResource, setLoading, update = fa const downloadProjectName = `${projectResource?.name}_${projectResource?.owner}_${projectResource?.release?.tag_name}`; existingResource?.forEach((element) => { if (downloadProjectName === element.name) { - throw new Error('Resource Already Exist'); + setLoading(false); + + // throw new Error('Resource Already Exist'); } }); diff --git a/renderer/src/components/Resources/ResourceUtils/RemoveResource.js b/renderer/src/components/Resources/ResourceUtils/RemoveResource.js index 8a3609d6..6fbdfaa7 100644 --- a/renderer/src/components/Resources/ResourceUtils/RemoveResource.js +++ b/renderer/src/components/Resources/ResourceUtils/RemoveResource.js @@ -102,6 +102,14 @@ function RemoveResource({ const [notify, setNotify] = React.useState(); const [openModal, setOpenModal] = React.useState(false); + /** + * 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; + // React.useEffect(() => { // }, []); @@ -122,6 +130,9 @@ function RemoveResource({ const newpath = localStorage.getItem('userPath'); const folder = path.join(newpath, packageInfo.name, 'users', `${user?.username}`, 'resources'); let resourceName = null; + let linkedResourceName = null; + let pathRelationFile = null; + let relationFileContent = null; switch (selectResource) { case 'obs': case 'bible': @@ -136,14 +147,27 @@ function RemoveResource({ case 'obs-tq': resourceName = resource?.projectDir; break; + case 'tir': + resourceName = resource?.projectDir; + pathRelationFile = path.join(folder, resourceName, 'ingredients', 'relation.txt'); + if (fs.existsSync(pathRelationFile)) { + relationFileContent = await fs.readFileSync(pathRelationFile, 'utf8'); + linkedResourceName = findFileByPartialName(fs, folder, relationFileContent.replace('\n', '').trim()); + } + break; default: break; } await fs.rmdir(path.join(folder, resourceName), { recursive: true }, async (err) => { - if (err) { + let linkedResErr; + if (!err && linkedResourceName) { + linkedResErr = await fs.rmdir(path.join(folder, linkedResourceName), { recursive: true }, (err) => err); + } + if (err || linkedResErr) { setOpenSnackBar(true); setNotify('failure'); setSnackText('Remove Resource Failed'); + return; // throw new Error(`Remove Resource failed : ${err}`); } // console.log('resource remove success'); diff --git a/renderer/src/components/Resources/ResourcesPopUp.js b/renderer/src/components/Resources/ResourcesPopUp.js index 680933b5..fec825cf 100644 --- a/renderer/src/components/Resources/ResourcesPopUp.js +++ b/renderer/src/components/Resources/ResourcesPopUp.js @@ -36,6 +36,7 @@ export default function ResourcesPopUp( const [downloading, setDownloading] = useState(false); const [currentDownloading, setCurrentDownloading] = useState(null); const [filteredResources, setFilteredResources] = useState({}); + const [filteredReposResourcelinks, setFilteredReposResourcelinks] = useState({}); const [filteredBibleObsAudio, setfilteredBibleObsAudio] = useState([]); const [currentFullResources, setCurrentFullResources] = useState([]); const [selectedPreProd, setSelectedPreProd] = useState(false); @@ -56,6 +57,9 @@ export default function ResourcesPopUp( }, } = useContext(ReferenceContext); + // New state to store the selected source + const [searchSource, setSearchSource] = useState('gitea'); + const handleRowSelect = (e, row, name, owner, flavorname, userOrCommon, offline = false) => { const offlineResource = offline ? { offline: true, data: offline } @@ -102,29 +106,72 @@ export default function ResourcesPopUp(

{t('label-resource')}

-
+
{selectResource !== 'local-helps' && ( - +
+ {/*
+ + +
*/} + {(selectResource === 'tn' || selectResource === 'tir') && ( +
+ + +
+ )} + {/* Existing SearchBar and selector in the same flex container */} + +
)} {(selectResource === 'obs' || selectResource === 'audio' || selectResource === 'bible' || selectResource === 'local-helps') @@ -152,6 +199,7 @@ export default function ResourcesPopUp( selectResource={selectResource} loading={loading} filteredResources={filteredResources} + filteredReposResourcelinks={filteredReposResourcelinks} downloading={downloading} removeSection={removeSection} handleRowSelect={handleRowSelect} @@ -165,8 +213,10 @@ export default function ResourcesPopUp( subMenuItems={subMenuItems} setCurrentFullResources={setCurrentFullResources} setFilteredResources={setFilteredResources} + setFilteredReposResourcelinks={setFilteredReposResourcelinks} setLoading={setLoading} setSubMenuItems={setSubMenuItems} + endPoint={searchSource} /> )}
diff --git a/renderer/src/components/Resources/ResourcesSideBar.js b/renderer/src/components/Resources/ResourcesSideBar.js index b3af01b9..02eec377 100644 --- a/renderer/src/components/Resources/ResourcesSideBar.js +++ b/renderer/src/components/Resources/ResourcesSideBar.js @@ -3,6 +3,7 @@ import { DocumentTextIcon, PhotoIcon, + CameraIcon, Square3Stack3DIcon, MicrophoneIcon, BookOpenIcon, @@ -62,6 +63,12 @@ export default function ResourcesSidebar({ resourceType: 'translationNote', Icon: DocumentTextIcon, }, + { + id: 'tir', + title: t('label-resource-tir'), + resourceType: 'translationImageResource', + Icon: CameraIcon, + }, { id: 'twlm', title: t('label-resource-twl'), diff --git a/renderer/src/components/Resources/useFetchTranslationResource.js b/renderer/src/components/Resources/useFetchTranslationResource.js index a5b5cb15..9117b1cd 100644 --- a/renderer/src/components/Resources/useFetchTranslationResource.js +++ b/renderer/src/components/Resources/useFetchTranslationResource.js @@ -7,29 +7,82 @@ function createData(name, language, owner) { name, language, owner, }; } -export const fetchTranslationResource = async (urlpath, setResource, selectResource, selectedPreProd, snackBarAction) => { + +export const fetchTranslationResource = async (urlpath, setResource, selectResource, selectedPreProd, snackBarAction, endPoint = 'gitea', setFilteredReposResourcelinks = null) => { logger.debug('ResourcesPopUp.js', `fetchTranslationResource : ${selectResource}`); // const baseUrl = 'https://git.door43.org/api/catalog/v5/search?'; // https://git.door43.org/api/v1/catalog/search?metadataType=rc // https://qa.door43.org/api/v1/repos/search?flavor=x-juxtalinear - const baseUrl = `${environment.GITEA_API_ENDPOINT}/catalog/search?metadataType=rc&metadataType=sb`; - let url = `${baseUrl}&subject=${urlpath}`; - if (selectedPreProd) { - url += '&stage=preprod'; - } - if (urlpath) { + if (endPoint === 'gitea') { + const baseUrl = `${environment.GITEA_API_ENDPOINT}/catalog/search?metadataType=rc&metadataType=sb`; + let url = `${baseUrl}&subject=${urlpath}`; + if (selectedPreProd) { + url += '&stage=preprod'; + } + if (urlpath) { + const resourceData = []; + try { + const fetchedData = await fetch(url); + const fetchedJson = await fetchedData.json(); + fetchedJson.data?.forEach(async (data) => { + const createdData = createData(data?.language_title, data?.language, data?.owner); + createdData.responseData = data; + resourceData.push(createdData); + }); + if (resourceData.length === fetchedJson.data?.length) { + setResource(resourceData); + } + } catch (err) { + snackBarAction?.setOpenSnackBar(true); + snackBarAction?.setError('failure'); + snackBarAction?.setSnackText('Load Online resource Failed. Might be due to internet'); + logger.debug('ResourcesPopUp.js', `fetchTranslationResource Error ${selectResource} : ${err}`); + } + } + } else if (endPoint === 'github') { + const baseUrl = `${environment.GITHUB_API_ENDPOINT}/orgs/Proskomma/repos?per_page=100`; + const url = baseUrl; + const resourceData = []; try { const fetchedData = await fetch(url); const fetchedJson = await fetchedData.json(); - fetchedJson.data?.forEach(async (data) => { - const createdData = createData(data?.language_title, data?.language, data?.owner); - createdData.responseData = data; + + // Filter repositories by topic (e.g., 'study-notes') + let filteredRepos; + let filteredReposResourcelinks; + if (selectResource === 'tn') { + filteredRepos = fetchedJson.filter((repo) => repo.topics.includes('burrito' && 'bcvnotes')); + } else if (selectResource === 'tir') { + filteredRepos = fetchedJson.filter((repo) => repo.topics.includes('burrito' && 'imagedict')); + filteredReposResourcelinks = fetchedJson.filter((repo) => repo.topics.includes('burrito' && 'resourcelinks')); + } + + let createdData; + filteredRepos.forEach((repo) => { + createdData = createData(repo?.name, repo?.language, repo?.owner?.login); + createdData.responseData = repo; + createdData.zipball_url = `${environment.GITHUB_SERVER}/Proskomma/${repo?.name}/archive/refs/heads/main.zip`; resourceData.push(createdData); }); - if (resourceData.length === fetchedJson.data?.length) { + + const resourceLinkData = []; + if (setFilteredReposResourcelinks !== null) { + filteredReposResourcelinks.forEach((repo) => { + createdData = createData(repo?.name, repo?.language, repo?.owner?.login); + createdData.responseData = repo; + createdData.zipball_url = `${environment.GITHUB_SERVER}/Proskomma/${repo?.name}/archive/refs/heads/main.zip`; + resourceLinkData.push(createdData); + }); + } + + if (resourceData.length === filteredRepos.length) { setResource(resourceData); } + + if (resourceLinkData.length > 0) { + setFilteredReposResourcelinks(resourceLinkData); + } } catch (err) { snackBarAction?.setOpenSnackBar(true); snackBarAction?.setError('failure'); diff --git a/renderer/src/components/Resources/useGetOnlineOfflineResource.js b/renderer/src/components/Resources/useGetOnlineOfflineResource.js index 1ce1b129..5e466a53 100644 --- a/renderer/src/components/Resources/useGetOnlineOfflineResource.js +++ b/renderer/src/components/Resources/useGetOnlineOfflineResource.js @@ -3,6 +3,7 @@ export default function getCurrentOnlineOfflineHelpsResources(selectResource) { const resources = [ { id: 'jxl', title: 'Juxtalinear', resource: juxtalinear }, { id: 'tn', title: t('label-resource-tn'), resource: translationNote }, + { id: 'tir', title: t('label-resource-tir'), resource: translationImageResources }, { id: 'twlm', title: t('label-resource-twl'), resource: translationWordList }, // { id: 'tw', title: t('label-resource-twlm'), resource: translationWord }, { id: 'tq', title: t('label-resource-tq'), resource: translationQuestion }, diff --git a/renderer/src/components/Resources/useHandleChangeQuery.js b/renderer/src/components/Resources/useHandleChangeQuery.js index b3874a1e..d93bb99e 100644 --- a/renderer/src/components/Resources/useHandleChangeQuery.js +++ b/renderer/src/components/Resources/useHandleChangeQuery.js @@ -1,7 +1,7 @@ /* eslint-disable no-nested-ternary */ const handleChangeQuery = (query, resourceData, selectResource, setFilteredResources, subMenuItems, setfilteredBibleObsAudio) => { const filtered = { offlineResource: [], onlineResource: { ...resourceData?.reference } || {} }; - if (['tn', 'tw', 'tq', 'ta', 'obs-tn', 'obs-tq', 'twlm', 'obs-twlm'].includes(selectResource?.toLowerCase())) { + if (['tn', 'tir', 'tw', 'tq', 'ta', 'obs-tn', 'obs-tq', 'twlm', 'obs-twlm'].includes(selectResource?.toLowerCase())) { if (query?.length > 0) { filtered.offlineResource = resourceData?.offlineResource?.filter((data) => { const meta = data?.value?.meta; diff --git a/renderer/src/translations/en.js b/renderer/src/translations/en.js index bdb5d7f2..fe0ed235 100644 --- a/renderer/src/translations/en.js +++ b/renderer/src/translations/en.js @@ -78,6 +78,7 @@ export const En = { 'label-resource-obs-tq': 'OBS Translation Questions', 'label-resource-obs': 'Open Bible Stories', 'label-resource-tn': 'Translation Notes', + 'label-resource-tir': 'Translation Image Resources', 'label-resource-twlm': 'Translation Words', 'label-resource-twl': 'Translation Word Links', 'label-resource-tq': 'Translation Questions', diff --git a/renderer/src/translations/id.js b/renderer/src/translations/id.js index c104f1b5..38cd9e3f 100644 --- a/renderer/src/translations/id.js +++ b/renderer/src/translations/id.js @@ -78,6 +78,7 @@ export const Id = { 'label-resource-obs-tq': 'OBS Translation Questions', 'label-resource-obs': 'Open Bible Stories', 'label-resource-tn': 'Translation Notes', + 'label-resource-tir': 'Translation Image Resources', 'label-resource-twlm': 'Translation Words', 'label-resource-twl': 'Translation Word Links', 'label-resource-tq': 'Translation Questions', diff --git a/styles/globals.css b/styles/globals.css index 0bc61f88..78b16c02 100755 --- a/styles/globals.css +++ b/styles/globals.css @@ -856,4 +856,107 @@ Select scribe theme Start align-self: "flex-end"; margin-top: 16; margin-right: 16; +} + +/** + IMAGE DISPLAY SECTION +*/ +/* Style for scrollable image gallery */ +.image-gallery { + display: flex; + justify-content: center; + flex-wrap: wrap; + gap: 20px; + padding: 20px; + background-color: #f5f5f5; +} + +.image-wrapper { + display: flex; + flex-direction: column; + align-items: center; + width: 200px; + transition: transform 0.3s ease-in-out; + background-color: white; + border-radius: 10px; + overflow: hidden; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); +} + +.image-wrapper:hover { + transform: translateY(-10px); + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2); +} + +.image-name { + font-size: 16px; + font-weight: bold; + color: #333; + margin: 10px 0; + text-align: center; + text-transform: capitalize; + background-color: rgba(135, 206, 250, 0.3); + width: 100%; + padding: 10px; +} + +.gallery-image { + width: 100%; + height: 150px; + object-fit: cover; + border-bottom: 1px solid #ddd; + cursor: pointer; + transition: transform 0.3s ease-in-out; +} + +.gallery-image:hover { + transform: scale(1.1); +} + +.zoomed-image-container { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.8); + display: flex; + justify-content: center; + align-items: center; + z-index: 1000; + cursor: zoom-out; +} + +.zoomed-image { + max-width: 90%; + max-height: 90%; + object-fit: contain; +} + +.close-zoom { + position: absolute; + top: 20px; + right: 20px; + font-size: 24px; + color: white; + cursor: pointer; +} + +.zoomed-content { + display: flex; + flex-direction: column; + align-items: center; + position: relative; + max-width: 90%; + max-height: 90%; +} + +.zoomed-image-name { + font-size: 34px; + color: white; + font-weight: bold; + margin-bottom: 10px; + text-align: center; + text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.7); + /* Slight shadow for readability */ } \ No newline at end of file From c126bc186b7260f1c0505ee015e063cc468d5cd8 Mon Sep 17 00:00:00 2001 From: danielc-n Date: Tue, 15 Oct 2024 17:28:01 +0200 Subject: [PATCH 05/20] change message from 'No resources image for this book' to 'No resources image for this verse' --- .../EditorPage/Reference/TranslationHelpsImageCard.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/renderer/src/components/EditorPage/Reference/TranslationHelpsImageCard.js b/renderer/src/components/EditorPage/Reference/TranslationHelpsImageCard.js index 20612c51..454195bc 100644 --- a/renderer/src/components/EditorPage/Reference/TranslationHelpsImageCard.js +++ b/renderer/src/components/EditorPage/Reference/TranslationHelpsImageCard.js @@ -110,7 +110,7 @@ export default function TranslationHelpsImageCard({
))} {imagePaths.length === 0 - && 'No resources image for this book'} + && 'No resources image for this verse'}
{zoomedImage && ( From e2ecab501d075fb75de15fa3a13d5fb774a7aee7 Mon Sep 17 00:00:00 2001 From: danielc-n Date: Tue, 15 Oct 2024 17:29:13 +0200 Subject: [PATCH 06/20] change message from 'No resources image for this verse' to 'No resources image for this Chapter/Verse' --- .../EditorPage/Reference/TranslationHelpsImageCard.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/renderer/src/components/EditorPage/Reference/TranslationHelpsImageCard.js b/renderer/src/components/EditorPage/Reference/TranslationHelpsImageCard.js index 454195bc..82a37849 100644 --- a/renderer/src/components/EditorPage/Reference/TranslationHelpsImageCard.js +++ b/renderer/src/components/EditorPage/Reference/TranslationHelpsImageCard.js @@ -110,7 +110,7 @@ export default function TranslationHelpsImageCard({
))} {imagePaths.length === 0 - && 'No resources image for this verse'} + && 'No resources image for this Chapter/Verse'}
{zoomedImage && ( From ef9373a585914b577fe727603c23c9ccc1ed24b7 Mon Sep 17 00:00:00 2001 From: danielc-n Date: Tue, 15 Oct 2024 17:59:55 +0200 Subject: [PATCH 07/20] now the tn are forced to be downloaded once before being displayed as a resource, and fixe a css problem --- .../src/components/Resources/ListResources.js | 2 +- styles/globals.css | 183 +++++++++--------- 2 files changed, 93 insertions(+), 92 deletions(-) diff --git a/renderer/src/components/Resources/ListResources.js b/renderer/src/components/Resources/ListResources.js index aadcde4d..f4d06c23 100644 --- a/renderer/src/components/Resources/ListResources.js +++ b/renderer/src/components/Resources/ListResources.js @@ -319,7 +319,7 @@ export const ListResources = ({ className={`${notes?.responseData?.stage === 'preprod' && 'bg-yellow-200'} hover:bg-primary hover:text-white group focus:outline-none`} id={notes.name} key={notes.name + notes.owner} - onClick={(e) => (selectResource !== 'tir' ? handleRowSelect(e, notes.language, `${filteredResources?.onlineResource?.title} ${notes.name}`, notes.owner, '') : handleDownloadHelpsResources(e, notes, filteredResources?.offlineResource))} + onClick={(e) => (selectResource === 'tir' || selectResource === 'tn' ? handleDownloadHelpsResources(e, notes, filteredResources?.offlineResource) : handleRowSelect(e, notes.language, `${filteredResources?.onlineResource?.title} ${notes.name}`, notes.owner, ''))} role="button" > diff --git a/styles/globals.css b/styles/globals.css index 0e29d21a..3f8f5931 100755 --- a/styles/globals.css +++ b/styles/globals.css @@ -837,109 +837,110 @@ Select scribe theme Start margin-right: 16; } - /** +} + + +/** IMAGE DISPLAY SECTION */ - /* Style for scrollable image gallery */ - .image-gallery { - display: flex; - justify-content: center; - flex-wrap: wrap; - gap: 20px; - padding: 20px; - background-color: #f5f5f5; - } - - .image-wrapper { - display: flex; - flex-direction: column; - align-items: center; - width: 200px; - transition: transform 0.3s ease-in-out; - background-color: white; - border-radius: 10px; - overflow: hidden; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); - } +/* Style for scrollable image gallery */ +.image-gallery { + display: flex; + justify-content: center; + flex-wrap: wrap; + gap: 20px; + padding: 20px; + background-color: #f5f5f5; +} - .image-wrapper:hover { - transform: translateY(-10px); - box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2); - } +.image-wrapper { + display: flex; + flex-direction: column; + align-items: center; + width: 200px; + transition: transform 0.3s ease-in-out; + background-color: white; + border-radius: 10px; + overflow: hidden; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); +} - .image-name { - font-size: 16px; - font-weight: bold; - color: #333; - margin: 10px 0; - text-align: center; - text-transform: capitalize; - background-color: rgba(135, 206, 250, 0.3); - width: 100%; - padding: 10px; - } +.image-wrapper:hover { + transform: translateY(-10px); + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2); +} - .gallery-image { - width: 100%; - height: 150px; - object-fit: cover; - border-bottom: 1px solid #ddd; - cursor: pointer; - transition: transform 0.3s ease-in-out; - } +.image-name { + font-size: 16px; + font-weight: bold; + color: #333; + margin: 10px 0; + text-align: center; + text-transform: capitalize; + background-color: rgba(135, 206, 250, 0.3); + width: 100%; + padding: 10px; +} - .gallery-image:hover { - transform: scale(1.1); - } +.gallery-image { + width: 100%; + height: 150px; + object-fit: cover; + border-bottom: 1px solid #ddd; + cursor: pointer; + transition: transform 0.3s ease-in-out; +} - .zoomed-image-container { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: rgba(0, 0, 0, 0.8); - display: flex; - justify-content: center; - align-items: center; - z-index: 1000; - cursor: zoom-out; - } +.gallery-image:hover { + transform: scale(1.1); +} - .zoomed-image { - max-width: 90%; - max-height: 90%; - object-fit: contain; - } +.zoomed-image-container { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.8); + display: flex; + justify-content: center; + align-items: center; + z-index: 1000; + cursor: zoom-out; +} - .close-zoom { - position: absolute; - top: 20px; - right: 20px; - font-size: 24px; - color: white; - cursor: pointer; - } +.zoomed-image { + max-width: 90%; + max-height: 90%; + object-fit: contain; +} - .zoomed-content { - display: flex; - flex-direction: column; - align-items: center; - position: relative; - max-width: 90%; - max-height: 90%; - } +.close-zoom { + position: absolute; + top: 20px; + right: 20px; + font-size: 24px; + color: white; + cursor: pointer; +} - .zoomed-image-name { - font-size: 34px; - color: white; - font-weight: bold; - margin-bottom: 10px; - text-align: center; - text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.7); - /* Slight shadow for readability */ - } +.zoomed-content { + display: flex; + flex-direction: column; + align-items: center; + position: relative; + max-width: 90%; + max-height: 90%; +} +.zoomed-image-name { + font-size: 34px; + color: white; + font-weight: bold; + margin-bottom: 10px; + text-align: center; + text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.7); + /* Slight shadow for readability */ } .editor-input { From 82642924ce33ff13cf11476b54be9cd4487c52cf Mon Sep 17 00:00:00 2001 From: danielc-n Date: Tue, 15 Oct 2024 18:10:20 +0200 Subject: [PATCH 08/20] changed a typo --- .../src/components/EditorPage/Reference/TranslationHelpsCard.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/renderer/src/components/EditorPage/Reference/TranslationHelpsCard.js b/renderer/src/components/EditorPage/Reference/TranslationHelpsCard.js index 15d20fa6..c3143063 100644 --- a/renderer/src/components/EditorPage/Reference/TranslationHelpsCard.js +++ b/renderer/src/components/EditorPage/Reference/TranslationHelpsCard.js @@ -232,7 +232,7 @@ export default function TranslationHelpsCard({ setOfflineItemsDisable(true); setOfflineMarkdown(filecontent); } else { - setOfflineMarkdown({ error: true, data: 'No Content Avaialble' }); + setOfflineMarkdown({ error: true, data: 'No Content Available' }); } } }); From b30a9bb5a9602a3707c5bd464e54386ae9491b02 Mon Sep 17 00:00:00 2001 From: danielc-n Date: Wed, 16 Oct 2024 13:34:52 +0200 Subject: [PATCH 09/20] now you can load even videos out of a burrito --- .../EditorPage/Reference/TranslationHelps.js | 8 +- .../Reference/TranslationHelpsImageCard.js | 164 +++++++++++++----- .../EditorPage/TextEditor/index.jsx | 4 +- .../src/components/Resources/ListResources.js | 2 +- .../Resources/useFetchTranslationResource.js | 2 +- .../src/components/context/ProjectContext.js | 14 +- renderer/src/translations/en.js | 2 +- renderer/src/translations/id.js | 2 +- styles/globals.css | 87 ++++------ 9 files changed, 178 insertions(+), 107 deletions(-) diff --git a/renderer/src/components/EditorPage/Reference/TranslationHelps.js b/renderer/src/components/EditorPage/Reference/TranslationHelps.js index e7169360..0a0c1a8b 100644 --- a/renderer/src/components/EditorPage/Reference/TranslationHelps.js +++ b/renderer/src/components/EditorPage/Reference/TranslationHelps.js @@ -46,14 +46,16 @@ const TranslationHelps = ({ 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'); - setImagesPath(pathToIngredients); 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); + 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!`); } } @@ -63,7 +65,7 @@ const TranslationHelps = ({ } getLinkedFolderPath(); - }, [imagesPath]); + }, [selectedResource, offlineResource]); return ( <> diff --git a/renderer/src/components/EditorPage/Reference/TranslationHelpsImageCard.js b/renderer/src/components/EditorPage/Reference/TranslationHelpsImageCard.js index 82a37849..bbe686e5 100644 --- a/renderer/src/components/EditorPage/Reference/TranslationHelpsImageCard.js +++ b/renderer/src/components/EditorPage/Reference/TranslationHelpsImageCard.js @@ -1,6 +1,8 @@ /* eslint-disable */ // import PropTypes from 'prop-types'; import React, { useEffect, useState } from 'react'; +import i18n from '../../../translations/i18n'; +const { net } = require('electron'); // import { // useContent, // } from 'translation-helps-rcl'; @@ -23,6 +25,57 @@ export default function TranslationHelpsImageCard({ const convertToFileUrl = (path) => `file://${path}`; + const CACHE_DIR = path.join(folderPath); + + const isVideoCached = (videoUrl) => { + const videoFileName = path.basename(videoUrl); + const localVideoPath = path.join(CACHE_DIR, videoFileName); + return fs.existsSync(localVideoPath) ? localVideoPath : null; + }; + + const downloadAndCacheVideo = (videoUrl) => { + return new Promise((resolve, reject) => { + const videoFileName = path.basename(videoUrl); + const localVideoPath = path.join(CACHE_DIR, videoFileName); + + const request = net.request(videoUrl); + const fileStream = fs.createWriteStream(localVideoPath); + + request.on('response', (response) => { + response.pipe(fileStream); + fileStream.on('finish', () => { + resolve(localVideoPath); + }); + }); + + request.on('error', (err) => { + reject(err); + }); + + request.end(); + }); + }; + + const loadVideo = async (videoUrl) => { + // Check if video is already cached + let localVideoPath = isVideoCached(videoUrl); + + if (!localVideoPath) { + // If not cached, download and cache the video + console.log(`Downloading and caching video: ${videoUrl}`); + localVideoPath = await downloadAndCacheVideo(videoUrl); + } + + return localVideoPath; + }; + + + + + if (!fs.existsSync(CACHE_DIR)) { + fs.mkdirSync(CACHE_DIR); + } + /** * Function to get image paths from a TSV file based on book code, chapter, and verse. * @@ -32,55 +85,67 @@ export default function TranslationHelpsImageCard({ const fs = window.require('fs'); const path = require('path'); const filePath = path.join(folderPath, `${projectId}.tsv`); - const metadataJson = JSON.parse(fs.readFileSync(path.join(linkedFolderPath, '..', 'metadata.json'))); + let metadataJson = JSON.parse(fs.readFileSync(path.join(linkedFolderPath, '..', 'metadata.json'))); + const reference = `${chapter}:${verse}`; + const finalImagePaths = []; // Check if the file exists if (!fs.existsSync(filePath)) { return []; } - - const reference = `${chapter}:${verse}`; - const finalImagePaths = []; - + + // Read the TSV file const fileContent = fs.readFileSync(filePath, 'utf-8'); - + // Split the content into lines const lines = fileContent.trim().split('\n'); - - // Loop through each line, starting from 1 to skip the header + let columns; let realPath; let dashedName; let dashedNameSplited; let localizedNameTmp; - for (let i = 1; i < lines.length; i++) { - columns = lines[i].split('\t'); - - // Check if the reference matches - if (columns[0] === reference) { - // Add the last column (image path) to the list - dashedName = columns[columns.length - 1].split('images/')[1]; - dashedNameSplited = dashedName.split('/')[1]; - localizedNameTmp = metadataJson.localizedNames[dashedNameSplited]; - realPath = convertToFileUrl(path.join(linkedFolderPath, `${dashedName }.jpg`)); - if (localizedNameTmp) { - localizedNameTmp = localizedNameTmp.short.en; - finalImagePaths.push([localizedNameTmp, realPath]); - } else { - finalImagePaths.push([dashedNameSplited, realPath]); + if (linkedFolderPath === '') { + metadataJson = JSON.parse(fs.readFileSync(path.join(folderPath, '..', 'metadata.json'))); + // Loop through each line, starting from 1 to skip the header + for (let i = 1; i < lines.length; i++) { + columns = lines[i].split('\t'); + // Check if the reference matches + if (columns[0] === reference) { + // Add the last column (image path) to the list + dashedName = columns[columns.length - 1].split('/').at(-1).split('.')[0]; + localizedNameTmp = metadataJson.localizedNames[dashedName]?.short[i18n.language] ?? ''; + finalImagePaths.push([localizedNameTmp, columns[columns.length - 1]]); + } + } + } else { + // Loop through each line, starting from 1 to skip the header + for (let i = 1; i < lines.length; i++) { + columns = lines[i].split('\t'); + + // Check if the reference matches + if (columns[0] === reference) { + // Add the last column (image path) to the list + dashedName = columns[columns.length - 1].split('images/')[1]; + dashedNameSplited = dashedName.split('/')[1]; + localizedNameTmp = metadataJson.localizedNames[dashedNameSplited]; + realPath = convertToFileUrl(path.join(linkedFolderPath, `${dashedName }.jpg`)); + if (localizedNameTmp) { + localizedNameTmp = localizedNameTmp.short[i18n.language] ?? localizedNameTmp.short.en ?? ''; + finalImagePaths.push([localizedNameTmp, realPath]); + } else { + finalImagePaths.push([dashedNameSplited, realPath]); + } } } } - return finalImagePaths; }; useEffect(() => { - if (linkedFolderPath && linkedFolderPath !== '') { - setImagePaths(getImagePathsFromTSV()); - } - }, [chapter, verse, projectId]); + setImagePaths(getImagePathsFromTSV()); + }, [chapter, verse, projectId, linkedFolderPath]); const [zoomedImage, setZoomedImage] = useState(null); @@ -96,24 +161,33 @@ export default function TranslationHelpsImageCard({
{imagePaths.length > 0 && imagePaths - .sort((a, b) => a[0].localeCompare(b[0])) + .sort((a, b) => a[0].localeCompare(b[0])) // Sort by name .map((imagePath, index) => (
{imagePath[0]}
- {`Image handleImageClick(imagePath[1], imagePath[0])} - /> + {/* Check if the path is a video (e.g., ends with .mp4) */} + {imagePath[1].match(/\.(mp4|webm|ogg)$/i) ? ( +
))} - {imagePaths.length === 0 - && 'No resources image for this Chapter/Verse'} + {imagePaths.length === 0 && "No resources image/video for this Chapter/Verse"}
- - {zoomedImage && ( + + {zoomedImage && !zoomedImage.src.match(/\.(mp4|webm|ogg)$/i) && (
X @@ -122,6 +196,16 @@ export default function TranslationHelpsImageCard({
)} + + {zoomedImage && zoomedImage.src.match(/\.(mp4|webm|ogg)$/i) && ( +
+
+ X +
{zoomedImage.title}
+
+
+ )}
); } diff --git a/renderer/src/components/EditorPage/TextEditor/index.jsx b/renderer/src/components/EditorPage/TextEditor/index.jsx index b534a997..34573e64 100644 --- a/renderer/src/components/EditorPage/TextEditor/index.jsx +++ b/renderer/src/components/EditorPage/TextEditor/index.jsx @@ -27,6 +27,8 @@ export default function TextEditor() { const { state: { bookId: defaultBookId, selectedFont, editorFontSize, projectScriptureDir, + chapter, + verse, }, actions: { handleSelectedFont, onChangeChapter, onChangeVerse, handleEditorFontSize, @@ -71,7 +73,7 @@ export default function TextEditor() { const handleUsjChange = useMemo( () => debounce(async (updatedUsj) => { updateCacheNSaveFile(updatedUsj, book); - console.log('usj updated', updatedUsj); + // console.log('usj updated', updatedUsj); }, 1000), [book], ); diff --git a/renderer/src/components/Resources/ListResources.js b/renderer/src/components/Resources/ListResources.js index f4d06c23..ab37a618 100644 --- a/renderer/src/components/Resources/ListResources.js +++ b/renderer/src/components/Resources/ListResources.js @@ -157,7 +157,7 @@ export const ListResources = ({ const reference = resources.find((r) => r.id === selectResource); // filter for a resource OR an x-bcvnotes burrito // const offlineResource = subMenuItems ? subMenuItems.filter((item) => (item?.value?.type?.flavorType?.flavor?.name === 'x-bcvnotes' || item?.value?.type?.flavorType?.flavor?.name === 'x-resourcelinks') || (item?.value?.agOffline && item?.value?.dublin_core?.identifier === selectResource)) : []; - const offlineResource = subMenuItems ? subMenuItems.filter((item) => (item?.value?.agOffline && item?.value?.dublin_core?.identifier === selectResource) || (item?.value?.type?.flavorType?.flavor?.name === 'x-bcvnotes' && selectResource === 'tn') || (item?.value?.type?.flavorType?.flavor?.name === 'x-imagedict' && selectResource === 'tir')) : []; + const offlineResource = subMenuItems ? subMenuItems.filter((item) => (item?.value?.agOffline && item?.value?.dublin_core?.identifier === selectResource) || (item?.value?.type?.flavorType?.flavor?.name === 'x-bcvnotes' && selectResource === 'tn') || ((item?.value?.type?.flavorType?.flavor?.name === 'x-imagedict' || item?.value?.type?.flavorType?.flavor?.name === 'x-videolinks') && selectResource === 'tir')) : []; return { reference, offlineResource }; }; diff --git a/renderer/src/components/Resources/useFetchTranslationResource.js b/renderer/src/components/Resources/useFetchTranslationResource.js index 9117b1cd..701cb0ad 100644 --- a/renderer/src/components/Resources/useFetchTranslationResource.js +++ b/renderer/src/components/Resources/useFetchTranslationResource.js @@ -54,7 +54,7 @@ export const fetchTranslationResource = async (urlpath, setResource, selectResou if (selectResource === 'tn') { filteredRepos = fetchedJson.filter((repo) => repo.topics.includes('burrito' && 'bcvnotes')); } else if (selectResource === 'tir') { - filteredRepos = fetchedJson.filter((repo) => repo.topics.includes('burrito' && 'imagedict')); + filteredRepos = fetchedJson.filter((repo) => repo.topics.includes('burrito' && 'imagedict') || repo.topics.includes('burrito' && 'videolinks') ); filteredReposResourcelinks = fetchedJson.filter((repo) => repo.topics.includes('burrito' && 'resourcelinks')); } diff --git a/renderer/src/components/context/ProjectContext.js b/renderer/src/components/context/ProjectContext.js index 83d0d8ff..d139a953 100644 --- a/renderer/src/components/context/ProjectContext.js +++ b/renderer/src/components/context/ProjectContext.js @@ -7,7 +7,7 @@ import { isElectron } from '../../core/handleElectron'; import * as logger from '../../logger'; import { saveProjectsMeta, saveSupabaseProjectsMeta } from '../../core/projects/saveProjetcsMeta'; import { environment } from '../../../environment'; -import staicLangJson from '../../lib/lang/langNames.json'; +import staticLangJson from '../../lib/lang/langNames.json'; import packageInfo from '../../../../package.json'; import { @@ -28,7 +28,7 @@ const ProjectContextProvider = ({ children }) => { const [drawer, setDrawer] = useState(false); const [scrollLock, setScrollLock] = useState(false); const [sideTabTitle, setSideTabTitle] = useState('New'); - const [languages, setLanguages] = useState(staicLangJson); + const [languages, setLanguages] = useState(staticLangJson); const [language, setLanguage] = useState({}); const [customLanguages, setCustomLanguages] = useState([]); @@ -150,7 +150,7 @@ const ProjectContextProvider = ({ children }) => { }); } }; - const concatLanguages = async (json, staicLangJson) => { + const concatLanguages = async (json, staticLangJson) => { logger.debug('ProjectContext.js', 'In concat languages'); const userlanguages = []; json.history?.languages?.forEach((userLang) => { @@ -163,9 +163,9 @@ const ProjectContextProvider = ({ children }) => { userlanguages.push(obj); }); const concatedLang = userlanguages.length > 0 - ? (staicLangJson) + ? (staticLangJson) .concat(userlanguages) - : staicLangJson; + : staticLangJson; return { concatedLang, userlanguages }; }; @@ -211,7 +211,7 @@ const ProjectContextProvider = ({ children }) => { .concat(json.history?.textTranslation.canonSpecification) : advanceSettings.canonSpecification); // concat static and custom languages - const langFilter = await concatLanguages(json, staicLangJson); + const langFilter = await concatLanguages(json, staticLangJson); const filteredLang = langFilter.concatedLang.filter((lang) => lang?.ang.trim() !== ''); setLanguages([...filteredLang]); setCustomLanguages(langFilter.userlanguages); @@ -262,7 +262,7 @@ const ProjectContextProvider = ({ children }) => { .concat(json.history?.textTranslation.canonSpecification) : advanceSettings.canonSpecification); // concat static and custom languages - const langFilter = await concatLanguages(json, staicLangJson); + const langFilter = await concatLanguages(json, staticLangJson); const filteredLang = langFilter.concatedLang.filter((lang) => lang?.ang.trim() !== ''); setLanguages([...filteredLang]); setCustomLanguages(langFilter.userlanguages); diff --git a/renderer/src/translations/en.js b/renderer/src/translations/en.js index fe0ed235..2592ba8e 100644 --- a/renderer/src/translations/en.js +++ b/renderer/src/translations/en.js @@ -78,7 +78,7 @@ export const En = { 'label-resource-obs-tq': 'OBS Translation Questions', 'label-resource-obs': 'Open Bible Stories', 'label-resource-tn': 'Translation Notes', - 'label-resource-tir': 'Translation Image Resources', + 'label-resource-tir': 'Translation Multimedia Resources', 'label-resource-twlm': 'Translation Words', 'label-resource-twl': 'Translation Word Links', 'label-resource-tq': 'Translation Questions', diff --git a/renderer/src/translations/id.js b/renderer/src/translations/id.js index 38cd9e3f..88809779 100644 --- a/renderer/src/translations/id.js +++ b/renderer/src/translations/id.js @@ -78,7 +78,7 @@ export const Id = { 'label-resource-obs-tq': 'OBS Translation Questions', 'label-resource-obs': 'Open Bible Stories', 'label-resource-tn': 'Translation Notes', - 'label-resource-tir': 'Translation Image Resources', + 'label-resource-tir': 'Translation Multimedia Resources', 'label-resource-twlm': 'Translation Words', 'label-resource-twl': 'Translation Word Links', 'label-resource-tq': 'Translation Questions', diff --git a/styles/globals.css b/styles/globals.css index 3f8f5931..3241feed 100755 --- a/styles/globals.css +++ b/styles/globals.css @@ -841,58 +841,40 @@ Select scribe theme Start /** - IMAGE DISPLAY SECTION + START IMAGE DISPLAY SECTION */ /* Style for scrollable image gallery */ -.image-gallery { - display: flex; - justify-content: center; - flex-wrap: wrap; - gap: 20px; - padding: 20px; - background-color: #f5f5f5; -} - .image-wrapper { display: flex; flex-direction: column; align-items: center; - width: 200px; - transition: transform 0.3s ease-in-out; - background-color: white; - border-radius: 10px; - overflow: hidden; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); -} - -.image-wrapper:hover { - transform: translateY(-10px); - box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2); + margin-bottom: 20px; } .image-name { font-size: 16px; font-weight: bold; - color: #333; - margin: 10px 0; - text-align: center; - text-transform: capitalize; - background-color: rgba(135, 206, 250, 0.3); - width: 100%; - padding: 10px; + margin-bottom: 10px; } .gallery-image { - width: 100%; - height: 150px; + width: 300px; + height: auto; object-fit: cover; - border-bottom: 1px solid #ddd; cursor: pointer; - transition: transform 0.3s ease-in-out; + transition: transform 0.2s; } .gallery-image:hover { - transform: scale(1.1); + transform: scale(1.05); +} + +.gallery-video { + width: 300px; + height: auto; + border-radius: 10px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); + outline: none; } .zoomed-image-container { @@ -906,7 +888,13 @@ Select scribe theme Start justify-content: center; align-items: center; z-index: 1000; - cursor: zoom-out; +} + +.zoomed-content { + display: flex; + flex-direction: column; + align-items: center; + position: relative; } .zoomed-image { @@ -915,33 +903,28 @@ Select scribe theme Start object-fit: contain; } +.zoomed-video { + max-width: 90%; + max-height: 90%; + border-radius: 10px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.5); + outline: none; +} + .close-zoom { position: absolute; - top: 20px; + top: 10px; right: 20px; font-size: 24px; color: white; cursor: pointer; + z-index: 1001; } -.zoomed-content { - display: flex; - flex-direction: column; - align-items: center; - position: relative; - max-width: 90%; - max-height: 90%; -} -.zoomed-image-name { - font-size: 34px; - color: white; - font-weight: bold; - margin-bottom: 10px; - text-align: center; - text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.7); - /* Slight shadow for readability */ -} +/** + END IMAGE DISPLAY SECTION +*/ .editor-input { text-align: start; From 60c11c078433546922f4fb2d1dde7a0b873c6d85 Mon Sep 17 00:00:00 2001 From: danielc-n Date: Wed, 16 Oct 2024 15:55:14 +0200 Subject: [PATCH 10/20] implemented caching for videos --- main/index.js | 33 ++++++ .../EditorPage/Reference/TranslationHelps.js | 2 + .../Reference/TranslationHelpsImageCard.js | 100 ++++++------------ styles/globals.css | 18 +++- 4 files changed, 83 insertions(+), 70 deletions(-) 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/src/components/EditorPage/Reference/TranslationHelps.js b/renderer/src/components/EditorPage/Reference/TranslationHelps.js index 0a0c1a8b..b04e789c 100644 --- a/renderer/src/components/EditorPage/Reference/TranslationHelps.js +++ b/renderer/src/components/EditorPage/Reference/TranslationHelps.js @@ -48,7 +48,9 @@ const TranslationHelps = ({ const pathToIngredients = path.resolve(resourceDirPath, offlineResource.data.projectDir, 'ingredients'); if (pathToIngredients) { const pathRelationFile = path.resolve(pathToIngredients, 'relation.txt'); + if (fs.existsSync(pathRelationFile)) { + console.log("HERE WHAT NOPE") setImagesPath(pathToIngredients); const relationFileContent = fs.readFileSync(pathRelationFile, 'utf8'); const fileName = findFileByPartialName(fs, path.resolve(resourceDirPath), relationFileContent.trim()); diff --git a/renderer/src/components/EditorPage/Reference/TranslationHelpsImageCard.js b/renderer/src/components/EditorPage/Reference/TranslationHelpsImageCard.js index bbe686e5..584eafb4 100644 --- a/renderer/src/components/EditorPage/Reference/TranslationHelpsImageCard.js +++ b/renderer/src/components/EditorPage/Reference/TranslationHelpsImageCard.js @@ -2,17 +2,30 @@ // import PropTypes from 'prop-types'; import React, { useEffect, useState } from 'react'; import i18n from '../../../translations/i18n'; -const { net } = require('electron'); -// import { -// useContent, -// } from 'translation-helps-rcl'; -// import localForage from 'localforage'; -// import LoadingScreen from '@/components/Loading/LoadingScreen'; -// import ReferenceCard from './ReferenceCard'; -// import * as logger from '../../../logger'; -// import packageInfo from '../../../../../package.json'; -// import TabSelector from './TabSelector'; -// import './TranslationHelpsImageCard.css'; // Include CSS styles + +const VideoPlayer = ({ videoUrl }) => { + 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 ( +