Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ADD] Native File System support to reload zim file #1131

Merged
merged 47 commits into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
e126ea2
[ADD] event listener for FS API
Rishabhg71 Oct 10, 2023
cae1b9b
[ADD] cache API forked from `kiwix-js-windows`
Rishabhg71 Oct 10, 2023
050815b
[ADD] Zim File handler saving in indexDB
Rishabhg71 Oct 11, 2023
e396375
[FINALIZE] File system api picker and saving handles in indexDB
Rishabhg71 Oct 12, 2023
decafcd
[ADD] Select for zim files
Rishabhg71 Oct 12, 2023
e9bf591
[ADD] Directory select and load
Rishabhg71 Oct 15, 2023
f05f314
[REFACTOR] messy code cleanup
Rishabhg71 Oct 15, 2023
4791acf
[FIX] selection bug in folder drag
Rishabhg71 Oct 17, 2023
4faf5f0
[ADD] support for webkit
Rishabhg71 Oct 17, 2023
e5ae1ca
[FIX] no files on folder drop webkit
Rishabhg71 Oct 18, 2023
96e7b8a
[FIX] Split zim working for webkit
Rishabhg71 Oct 18, 2023
c8693ee
[ADD] Split zim for FS API
Rishabhg71 Oct 19, 2023
b39180b
[FIX] dropdown UI bug when multiple files are selected
Rishabhg71 Oct 19, 2023
b13861b
[FIX] new files added to serviceworker
Rishabhg71 Oct 19, 2023
0eb2e54
[FIX] zim not loading in tests
Rishabhg71 Oct 19, 2023
05df87f
[REFACTOR] Final touches + jsdoc update
Rishabhg71 Oct 19, 2023
29c207f
[FIX] making codefactor happy
Rishabhg71 Oct 19, 2023
320d1e7
[FIX] Ui changes requested by @jaifroid
Rishabhg71 Oct 19, 2023
4661508
[FIX] Edge File drop not working
Rishabhg71 Oct 20, 2023
16f8cd4
[REFACTOR] semi colon in end of every line
Rishabhg71 Oct 20, 2023
bf57673
[FIX] wrong file count
Rishabhg71 Oct 20, 2023
2458223
[REFACTOR] using already existing select
Rishabhg71 Oct 22, 2023
b01f962
[REFACTOR] saving and getting filenames from localstorage
Rishabhg71 Oct 22, 2023
6778907
[REFACTOR] `fileSystem.js` code moved to `abstractFilesystemAccess.js`
Rishabhg71 Oct 23, 2023
ff3bc2d
[REFACTOR] making codefactor happy
Rishabhg71 Oct 23, 2023
88a5088
[FIX] db name `undefined` changed
Rishabhg71 Oct 23, 2023
b7729b9
[REFACTORS] minor refactors for code review
Rishabhg71 Oct 24, 2023
512a99b
[REFACTOR] API support flag moved inside `params`
Rishabhg71 Oct 26, 2023
8049409
[FIX] firefox os race condition
Rishabhg71 Oct 27, 2023
16ec687
[FIX] FFOS UI and race around
Rishabhg71 Oct 30, 2023
96839f7
[ADD] translations for other languages
Rishabhg71 Oct 31, 2023
6e0cc44
[ADD] missed elements for translations
Rishabhg71 Oct 31, 2023
6c567f0
[REFACTOR] minor fixes and comments
Rishabhg71 Oct 31, 2023
662f162
Merge branch 'main' into fs-support
Rishabhg71 Oct 31, 2023
1c5e419
requested changes by @jaifroid
Rishabhg71 Oct 31, 2023
6ededf1
[REFACTOR] translations updated
Rishabhg71 Oct 31, 2023
600e831
[FIX] edge 18 file picker
Rishabhg71 Oct 31, 2023
c65ab0d
[FIX] translation errors
Rishabhg71 Nov 2, 2023
879db53
[FIX] option not loading on refresh
Rishabhg71 Nov 3, 2023
40b44ce
[FIX] UI placement fix
Rishabhg71 Nov 3, 2023
c74e04d
[ADD] file list save for webkit dir
Rishabhg71 Nov 4, 2023
5f8ce32
[FIX] single file picker for webkit
Rishabhg71 Nov 4, 2023
a55d9d2
[FIX] XSS and mobile phone folder picker disabled
Rishabhg71 Nov 5, 2023
bb1d61b
[FIX] auto load zim on Folder select
Rishabhg71 Nov 5, 2023
9f999b2
Merge branch 'main' into fs-support
Rishabhg71 Nov 5, 2023
b18fe52
Refactor file selection logic for better
Rishabhg71 Nov 7, 2023
b23f46d
[FIX] safari file picker not showing
Rishabhg71 Nov 7, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,4 @@ www-ghdeploy
/.vs/
/.vscode/
scripts/github_token
.prettierrc
6 changes: 6 additions & 0 deletions i18n/en.jsonp.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions i18n/es.jsonp.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions i18n/fr.jsonp.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions service-worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ const precacheFiles = [
'www/js/lib/abstractFilesystemAccess.js',
'www/js/lib/arrayFromPolyfill.js',
'www/js/lib/filecache.js',
'www/js/lib/cache.js',
'www/js/lib/promisePolyfill.js',
'www/js/lib/settingsStore.js',
'www/js/lib/translateUI.js',
Expand Down
5 changes: 4 additions & 1 deletion tests/e2e/spec/gutenberg_ro.e2e.spec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* legacy-ray_charles.e2e.spec.js : End-to-end tests implemented with Selenium WebDriver and Mocha
*
* Copyright 2023 Jaifroid, RG7279805 and contributors
* Copyright 2023 Jaifroid, Rishabhg71 and contributors
* Licence GPL v3:
*
* This file is part of Kiwix.
Expand Down Expand Up @@ -192,6 +192,9 @@ function runTests (driver, modes) {
const archiveFiles = await driver.findElement(By.id('archiveFiles'));
if (!isFileLoaded) await archiveFiles.sendKeys(gutenbergRoBaseFile);
filesLength = await driver.executeScript('return document.getElementById("archiveFiles").files.length');
// In new browsers Files are loaded using the FileSystem API, so we have to set the local archives using JavaScript
// which were selected using the file input
await driver.executeScript('window.setLocalArchiveFromFileSelect();');
// Check that we loaded 1 file
assert.equal(1, filesLength);
} else {
Expand Down
3 changes: 3 additions & 0 deletions tests/e2e/spec/legacy-ray_charles.e2e.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,9 @@ function runTests (driver, modes) {
filesLength = await driver.executeScript('return document.getElementById("archiveFiles").files.length');
return filesLength === 15;
}, 5000);
// In new browsers Files are loaded using the FileSystem API, so we have to set the local archives using JavaScript
// which were selected using the file input
await driver.executeScript('window.setLocalArchiveFromFileSelect();');
// Check that we loaded 15 files
assert.equal(15, filesLength);
} else {
Expand Down
21 changes: 21 additions & 0 deletions www/css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,21 @@
margin: 0 1rem;
}

#filesSelectionInfoAndCount {
padding-top: 0.5rem;
}
#filesSelectionInfoAndCount p{
display: inline;
}

#archiveList {
width: 60%;
min-width: auto;
}
Rishabhg71 marked this conversation as resolved.
Show resolved Hide resolved

Rishabhg71 marked this conversation as resolved.
Show resolved Hide resolved
#rescanButtonAndText button {
margin-top: 0.5rem;
}
/* Custom file input */

input[type="file"] {
Expand Down Expand Up @@ -330,4 +345,10 @@ button {
padding-bottom: 1px !important;
font-size: 16px !important;
}

#archiveList {
width: 100%;
min-width: auto;
}

}
29 changes: 20 additions & 9 deletions www/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -477,13 +477,17 @@ <h2 data-i18n="configure-title">Configuration</h2>
<a href="#" id="selectorsDisplayLink" data-i18n="configure-selectordisplay-link">display file selectors</a>.</p>
</div>
<div id="openLocalFiles" style="display: none;">
<p data-i18n="configure-select-instructions">Please select or drag and drop a .zim file (or all the .zimaa, .zimab etc in
<p data-i18n="configure-select-instructions" id="selectInstructions">Please select or drag and drop a .zim file (or all the .zimaa, .zimab etc in
case of a split ZIM file):</p>
<span>
<span id="fileSelectionButtonContainer">
<label class="btn btn-light custom-file-upload">
<input type="file" id="archiveFiles" multiple class="btn"
<input type="file" id="archiveFiles" multiple class="btn"
accept="application/octet-stream,.zim,.zimaa,.zimab,.zimac,.zimad,.zimae,.zimaf,.zimag,.zimah,.zimai,.zimaj,.zimak,.zimal,.zimam,.ziman,.zimao,.zimap,.zimaq,.zimar,.zimas,.zimat,.zimau,.zimav,.zimaw,.zimax,.zimay,.zimaz,.zimba,.zimbb,.zimbc,.zimbd,.zimbe,.zimbf,.zimbg,.zimbh,.zimbi,.zimbj,.zimbk,.zimbl,.zimbm,.zimbn,.zimbo,.zimbp,.zimbq,.zimbr,.zimbs,.zimbt,.zimbu,.zimbv,.zimbw,.zimbx,.zimby,.zimbz" />
<span data-i18n="home-btn-fileselect">Select ZIM file(s)</span>
<span data-i18n="home-btn-fileselect">Select ZIM File(s)</span>
</label>
<label class="btn btn-light custom-file-upload" id="folderSelect" data-i18n="" style="display: none;" data-i18n="configure-btn-folderselect">
<input type="file" id="archiveFolders" style="display: none;" webkitdirectory="true"/>
<span data-i18n="configure-btn-folderselect">Select Folder</span>
</label>
<label class="btn btn-light custom-file-upload" id="libraryBtn" data-i18n="configure-btn-library">
Browse ZIM Library
Expand All @@ -497,13 +501,20 @@ <h2 data-i18n="configure-title">Configuration</h2>
<br /> Scanning for archives... Please wait <img src="img/spinner.gif" alt="Please wait..." />
</div>
<div id="chooseArchiveFromLocalStorage" style="display: none;">
<br /> Please select the archive you want to use : <select id="archiveList"
class="form-control"></select>
<br /><button type="button" class="btn btn-light" id="btnRescanDeviceStorage">Rescan</button>
Rescans your SD Cards and internal memory
<div id="filesSelectionInfoAndCount">
<p id="numberOfFilesCount" style="display: none;">0</p>
<p id="fileCountDisplay" style="display: none;" data-i18n="configure-select-file-numbers">
archive(s) found in selected location.&nbsp;
</p>
<p data-i18n="configure-select-file-instructions">Please select the archive you want to use: </p>
</div>
<select id="archiveList" class="form-control"></select>
<span id="rescanButtonAndText" style="display: none;">
Rishabhg71 marked this conversation as resolved.
Show resolved Hide resolved
<button type="button" class="btn btn-light" id="btnRescanDeviceStorage" data-i18n="configure-btn-rescan">Rescan</button>
<p data-i18n="configure-about-rescan-tip">Rescans your SD Cards and internal memory</p>
</span>
</div>
</div>
<br />
<div class="container">
<h3 data-i18n="configure-display-settings-title">Display settings</h3>
<div class="card card-info" id="displaySettingsDiv">
Expand Down
127 changes: 120 additions & 7 deletions www/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ function resizeIFrame () {
document.addEventListener('DOMContentLoaded', function () {
getDefaultLanguageAndTranslateApp();
resizeIFrame();
abstractFilesystemAccess.loadPreviousZimFile();
});
window.addEventListener('resize', resizeIFrame);

Expand Down Expand Up @@ -1189,6 +1190,7 @@ window.onpopstate = function (event) {
function populateDropDownListOfArchives (archiveDirectories) {
document.getElementById('scanningForArchives').style.display = 'none';
document.getElementById('chooseArchiveFromLocalStorage').style.display = '';
document.getElementById('rescanButtonAndText').style.display = '';
var comboArchiveList = document.getElementById('archiveList');
comboArchiveList.options.length = 0;
for (var i = 0; i < archiveDirectories.length; i++) {
Expand Down Expand Up @@ -1281,11 +1283,26 @@ function resetCssCache () {
}
}

let webKitFileList = null
/**
* Displays the zone to select files from the archive
*/
function displayFileSelect () {
const isFireFoxOsNativeFileApiAvailable = typeof navigator.getDeviceStorages === 'function';
let isPlatformMobilePhone = false;
if (/Android/i.test(navigator.userAgent)) isPlatformMobilePhone = true;
if (/iphone|ipad|ipod/i.test(navigator.userAgent) || navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1) isPlatformMobilePhone = true;

console.debug(`File system api is ${params.isFileSystemApiSupported ? '' : 'not '}supported`);
console.debug(`Webkit directory api ${params.isWebkitDirApiSupported ? '' : 'not '}supported`);
console.debug(`Firefox os native file ${isFireFoxOsNativeFileApiAvailable ? '' : 'not '}support api`)

document.getElementById('openLocalFiles').style.display = 'block';
if ((params.isFileSystemApiSupported || params.isWebkitDirApiSupported) && !isPlatformMobilePhone) {
document.getElementById('chooseArchiveFromLocalStorage').style.display = '';
document.getElementById('folderSelect').style.display = '';
}

// Set the main drop zone
if (!params.disableDragAndDrop) {
configDropZone.addEventListener('dragover', handleGlobalDragover);
Expand All @@ -1300,8 +1317,89 @@ function displayFileSelect () {
});
globalDropZone.addEventListener('drop', handleFileDrop);
}
// This handles use of the file picker
document.getElementById('archiveFiles').addEventListener('change', setLocalArchiveFromFileSelect);

if (isFireFoxOsNativeFileApiAvailable) {
useLegacyFilePicker();
return;
}

document.getElementById('archiveList').addEventListener('change', async function (e) {
// handle zim selection from dropdown if multiple files are loaded via webkitdirectory or filesystem api
localStorage.setItem('previousZimFileName', e.target.value);
if (params.isFileSystemApiSupported) {
const files = await abstractFilesystemAccess.getSelectedZimFromCache(e.target.value)
setLocalArchiveFromFileList(files);
} else {
if (webKitFileList === null) {
const element = localStorage.getItem('zimFilenames').split('|').length === 1 ? 'archiveFiles' : 'archiveFolders';
if ('showPicker' in HTMLInputElement.prototype) {
document.getElementById(element).showPicker();
return;
}
document.getElementById(element).click()
return;
}
const files = abstractFilesystemAccess.getSelectedZimFromWebkitList(webKitFileList, e.target.value)
setLocalArchiveFromFileList(files);
}
});

if (params.isFileSystemApiSupported) {
// Handles Folder selection when showDirectoryPicker is supported
document.getElementById('folderSelect').addEventListener('click', async function (e) {
e.preventDefault();
const previousZimFiles = await abstractFilesystemAccess.selectDirectoryFromPickerViaFileSystemApi()
if (previousZimFiles.length !== 0) setLocalArchiveFromFileList(previousZimFiles);
})
}
if (params.isWebkitDirApiSupported) {
// Handles Folder selection when webkitdirectory is supported but showDirectoryPicker is not
document.getElementById('folderSelect').addEventListener('change', async function (e) {
e.preventDefault();
const filenames = [];

const previousZimFile = []
const lastFilename = localStorage.getItem('previousZimFileName') ?? '';
const filenameWithoutExtension = lastFilename.replace(/\.zim\w\w$/i, '');
const regex = new RegExp(`\\${filenameWithoutExtension}.zim\\w\\w$`, 'i');

for (const file of e.target.files) {
filenames.push(file.name);
if (regex.test(file.name) || file.name === lastFilename) previousZimFile.push(file);
}
webKitFileList = e.target.files;
localStorage.setItem('zimFilenames', filenames.join('|'));
// will load the old file if the selected folder contains the same file
if (previousZimFile.length !== 0) setLocalArchiveFromFileList(previousZimFile);
await abstractFilesystemAccess.updateZimDropdownOptions(filenames, previousZimFile.length !== 0 ? lastFilename : '');
})
}
if (params.isFileSystemApiSupported) {
// Handles File selection when showOpenFilePicker is supported and uses the filesystem api
document.getElementById('archiveFiles').addEventListener('click', async function (e) {
e.preventDefault();
const files = await abstractFilesystemAccess.selectFileFromPickerViaFileSystemApi(e);
setLocalArchiveFromFileList(files);
});
} else {
Rishabhg71 marked this conversation as resolved.
Show resolved Hide resolved
// Fallbacks to simple file input with multi file selection
useLegacyFilePicker();
}
}

/**
* Adds a event listener to the file input to handle file selection (if no other file picker is supported)
*/
function useLegacyFilePicker () {
// Fallbacks to simple file input with multi file selection
document.getElementById('archiveFiles').addEventListener('change', async function (e) {
if (params.isWebkitDirApiSupported || params.isFileSystemApiSupported) {
const activeFilename = e.target.files[0].name;
localStorage.setItem('zimFilenames', [activeFilename].join('|'));
await abstractFilesystemAccess.updateZimDropdownOptions([activeFilename], activeFilename);
}
setLocalArchiveFromFileSelect();
});
}

function handleGlobalDragover (e) {
Expand All @@ -1321,17 +1419,29 @@ function handleIframeDrop (e) {
e.preventDefault();
}

function handleFileDrop (packet) {
async function handleFileDrop (packet) {
packet.stopPropagation();
packet.preventDefault();
configDropZone.style.border = '';
var files = packet.dataTransfer.files;
document.getElementById('openLocalFiles').style.display = 'none';
document.getElementById('selectInstructions').style.display = 'none';
document.getElementById('fileSelectionButtonContainer').style.display = 'none';
document.getElementById('downloadInstruction').style.display = 'none';
document.getElementById('selectorsDisplay').style.display = 'inline';
setLocalArchiveFromFileList(files);
// This clears the display of any previously picked archive in the file selector
document.getElementById('archiveFiles').value = null;

// value will be set to true if a folder is dropped then there will be no need to
// call the `setLocalArchiveFromFileList`
let loadZim = true;

// no previous file will be loaded in case of FileSystemApi
if (params.isFileSystemApiSupported) loadZim = await abstractFilesystemAccess.handleFolderOrFileDropViaFileSystemAPI(packet);
else if (params.isWebkitDirApiSupported) {
const ret = await abstractFilesystemAccess.handleFolderOrFileDropViaWebkit(packet);
loadZim = ret.loadZim;
webKitFileList = ret.files;
}
if (loadZim) setLocalArchiveFromFileList(files);
}

document.getElementById('libraryBtn').addEventListener('click', function (e) {
Expand All @@ -1353,7 +1463,9 @@ document.getElementById('libraryBtn').addEventListener('click', function (e) {
// Add event listener to link which allows user to show file selectors
document.getElementById('selectorsDisplayLink').addEventListener('click', function (e) {
e.preventDefault();
document.getElementById('openLocalFiles').style.display = 'block';
document.getElementById('selectInstructions').style.display = 'block';
document.getElementById('downloadInstruction').style.display = 'block';
document.getElementById('fileSelectionButtonContainer').style.display = 'block';
document.getElementById('selectorsDisplay').style.display = 'none';
});

Expand Down Expand Up @@ -1392,6 +1504,7 @@ function archiveReadyCallback (archive) {
function setLocalArchiveFromFileSelect () {
setLocalArchiveFromFileList(document.getElementById('archiveFiles').files);
}
window.setLocalArchiveFromFileSelect = setLocalArchiveFromFileSelect;
Rishabhg71 marked this conversation as resolved.
Show resolved Hide resolved

/**
* Reads a remote archive with given URL, and returns the response in a Promise.
Expand Down
Loading