diff --git a/webui-src/app/files/files.css b/webui-src/app/files/files.css new file mode 100644 index 0000000..7d0c211 --- /dev/null +++ b/webui-src/app/files/files.css @@ -0,0 +1,24 @@ +.file-view { + width: 90%; + border-radius: 10px; + border: 1px solid #ccc; + margin-top: 2em; + padding: 1em; + animation: fadein 0.5s; +}.file-view > p { + margin: 0.5em; + font-size: 1.1em; +}.file-view > span { + margin: 0 1em; + font-size: 0.9em; +}.file-view > .progressbar { + width: 70%; +}.file-view button { + float: right; +} +.searchbar { + margin: 5px; + width: 40%; + box-shadow: 0 0 3px #aaa; +} + diff --git a/webui-src/app/files/files_downloads.js b/webui-src/app/files/files_downloads.js index 7c13ec4..f79ea00 100644 --- a/webui-src/app/files/files_downloads.js +++ b/webui-src/app/files/files_downloads.js @@ -3,8 +3,27 @@ let rs = require('rswebui'); let util = require('files_util') +function setHashDetail(hash, isNew = false) { + rs.rsJsonApiRequest( + '/rsFiles/FileDetails', { + hash, + hintflags: 16, // RS_FILE_HINTS_DOWNLOAD + }, + (fileStat) => { + if(!fileStat.retval) { + console.error('Error: Unknown hash in Downloads: ', hash); + return; + } + fileStat.info.isSearched = (isNew ? + true : + Downloads.statusMap[hash].isSearched); + Downloads.statusMap[hash] = fileStat.info; + }, + ); +} + let Downloads = { - statusMap: new Map(), + statusMap: {}, hashes: [], loadHashes() { @@ -16,48 +35,51 @@ let Downloads = { loadStatus() { Downloads.loadHashes(); - // TODO more comprehensive check - if(Downloads.hashes.length !== Downloads.statusMap.size) { - Downloads.statusMap.clear(); + let fileKeys = Object.keys(Downloads.statusMap); + if(Downloads.hashes.length !== fileKeys.length) { + // New file added + if(Downloads.hashes.length > fileKeys.length) { + let newHashes = util.compareArrays(Downloads.hashes, fileKeys); + for(let hash of newHashes) { + setHashDetail(hash, true); + } + } + // Existing file removed + else { + let oldHashes = util.compareArrays(fileKeys, Downloads.hashes); + for(let hash of oldHashes) { + delete Downloads.statusMap[hash]; + } + } } - for(let hash of Downloads.hashes) { - rs.rsJsonApiRequest( - '/rsFiles/FileDetails', { - hash, - hintflags: 16, // RS_FILE_HINTS_DOWNLOAD - }, - (fileStat) => Downloads.statusMap.set(hash, fileStat.info), - ); + for(let hash in Downloads.statusMap) { + setHashDetail(hash); } }, }; - -let backgroundCallback = function() { - this.loadStatus(); -}; -// Bind to Downloads so it doesn't lose scope -backgroundCallback = backgroundCallback.bind(Downloads); - -let Component = { - oninit: () => rs.setBackgroundTask( - backgroundCallback, - 1000, - () => { - return (m.route.get() === '/files') - } - ), - view: function() { - return m('.widget', [ +const Component = () => { + return { + oninit: () => rs.setBackgroundTask( + Downloads.loadStatus, + 1000, + () => { + return (m.route.get() === '/files') + } + ), + view: () => m('.widget', [ m('h3', 'Downloads'), m('hr'), - Array.from(Downloads.statusMap, - (fileStatus) => m(util.File, { - info: fileStatus[1], + Object.keys(Downloads.statusMap).map( + (hash) => m(util.File, { + info: Downloads.statusMap[hash] })), - ]); - }, + ]), + }; }; -module.exports = Component; +module.exports = { + Component, + list: Downloads.statusMap +}; diff --git a/webui-src/app/files/files_resolver.js b/webui-src/app/files/files_resolver.js index 7888f08..46d2e3d 100644 --- a/webui-src/app/files/files_resolver.js +++ b/webui-src/app/files/files_resolver.js @@ -3,12 +3,16 @@ let rs = require('rswebui'); let downloads = require('files_downloads'); let uploads = require('files_uploads'); +let util = require('files_util'); const Layout = { view: vnode => m('.tab-page', [ - m(downloads), - m(uploads), + m(util.SearchBar, { + list: Object.assign({}, downloads.list, uploads.list), + }), + m(downloads.Component), + m(uploads.Component), ]), }; diff --git a/webui-src/app/files/files_uploads.js b/webui-src/app/files/files_uploads.js index bb50907..f5dca98 100644 --- a/webui-src/app/files/files_uploads.js +++ b/webui-src/app/files/files_uploads.js @@ -3,8 +3,27 @@ let rs = require('rswebui'); let util = require('files_util') +function setHashDetail(hash, isNew = false) { + rs.rsJsonApiRequest( + '/rsFiles/FileDetails', { + hash, + hintflags: 16, // RS_FILE_HINTS_DOWNLOAD + }, + (fileStat) => { + if(!fileStat.retval) { + console.error('Error: Unknown hash in Uploads: ', hash); + return; + } + fileStat.info.isSearched = (isNew ? + true : + Uploads.statusMap[hash].isSearched); + Uploads.statusMap[hash] = fileStat.info; + }, + ); +} + let Uploads = { - statusMap: new Map(), + statusMap: {}, hashes: [], loadHashes() { @@ -16,29 +35,33 @@ let Uploads = { loadStatus() { Uploads.loadHashes(); - if(Uploads.hashes.length !== Uploads.statusMap.size) { - Uploads.statusMap.clear(); + let fileKeys = Object.keys(Uploads.statusMap); + if(Uploads.hashes.length !== fileKeys.length) { + // New file added + if(Uploads.hashes.length > fileKeys.length) { + let newHashes = util.compareArrays(Uploads.hashes, fileKeys); + for(let hash of newHashes) { + setHashDetail(hash, true); + } + } + // Existing file removed + else { + let oldHashes = util.compareArrays(fileKeys, Uploads.hashes); + for(let hash of oldHashes) { + delete Uploads.statusMap[hash]; + } + } + } + for(let hash in Uploads.statusMap) { + setHashDetail(hash); } - Uploads.hashes.map( - (hash) => rs.rsJsonApiRequest( - '/rsFiles/FileDetails', { - hash, - hintflags: 16, // RS_FILE_HINTS_DOWNLOAD TODO??? - }, - (fileStat) => Uploads.statusMap.set(hash, fileStat.info), - )); }, }; -let backgroundCallback = function() { - this.loadStatus(); -}; -backgroundCallback = backgroundCallback.bind(Uploads); - const Component = () => { return { oninit: () => rs.setBackgroundTask( - backgroundCallback, + Uploads.loadStatus, 1000, () => { return (m.route.get() === '/files') @@ -47,12 +70,16 @@ const Component = () => { view: () => m('.widget', [ m('h3', 'Uploads'), m('hr'), - Array.from(Uploads.statusMap, - (fileStatus) => m(util.File, { - info: fileStatus[1], + Object.keys(Uploads.statusMap).map( + (hash) => m(util.File, { + info: Uploads.statusMap[hash] })), - ]) + ]), }; -} -module.exports = Component; +}; + +module.exports = { + Component, + list: Uploads.statusMap +}; diff --git a/webui-src/app/files/files_util.js b/webui-src/app/files/files_util.js index ee2dbb7..6a2b713 100644 --- a/webui-src/app/files/files_util.js +++ b/webui-src/app/files/files_util.js @@ -105,9 +105,12 @@ const ProgressBar = () => { const File = () => { return { view: (v) => m('.file-view', { - key: v.attrs.info.hash + key: v.attrs.info.hash, + style: { + display: v.attrs.info.isSearched ? "block" : "none", + } }, [ - m('p', v.attrs.info.name), + m('p', v.attrs.info.fname), actionButton(v.attrs.info, 'cancel'), actionButton(v.attrs.info, v.attrs.info.downloadStatus === FT_STATE_PAUSED ? @@ -124,6 +127,36 @@ const File = () => { } }; +let searchString = ''; +const SearchBar = () => { + return { + oninit: (v) => console.log('srst=', searchString), + view: (v) => + m('input[type=text][placeholder=search].searchbar', { + value: searchString, + oninput: (e) => { + searchString = e.target.value; + for(let hash in v.attrs.list) { + if(v.attrs.list[hash].fname.indexOf(searchString) > -1) { + v.attrs.list[hash].isSearched = true; + } else { + v.attrs.list[hash].isSearched = false; + } + } + }, + }), + }; +}; + +function compareArrays(big, small) { + // Use filter on bigger array + // Pass `new Set(array_to_compare)` as second param to filter + // Source: https://stackoverflow.com/a/40538072/7683374 + return big.filter(function(val) { + return !this.has(val); + }, new Set(small)); +}; + module.exports = { RS_FILE_CTRL_PAUSE, RS_FILE_CTRL_START, @@ -137,5 +170,7 @@ module.exports = { FT_STATE_PAUSED, FT_STATE_CHECKING_HASH, File, + SearchBar, + compareArrays, };