Skip to content

Commit

Permalink
Native File System support to reload zim file and Webkit directory se…
Browse files Browse the repository at this point in the history
…lection(#1131)
  • Loading branch information
Rishabhg71 committed Nov 8, 2023
1 parent bd19ae7 commit e652d4b
Show file tree
Hide file tree
Showing 14 changed files with 1,441 additions and 49 deletions.
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;
}

#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;">
<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 {
// 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;

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

0 comments on commit e652d4b

Please sign in to comment.