From 248fbf19a47f689b1ef94a840324f7e97820abab Mon Sep 17 00:00:00 2001 From: mattk70 Date: Fri, 16 Feb 2024 22:21:02 +0000 Subject: [PATCH 1/4] Remember the do not track setting in the menu Add screen dimensions to tracking only send visit tracking if not optied out of tecking Alerts now toasts Don't call filter results on nocmig change if no file loaded Don't filter results if no file loaded when changing language Send local time in track request --- index.html | 6 +-- js/ui.js | 105 ++++++++++++++++++++++++++++++--------------------- js/worker.js | 14 +++---- package.json | 2 +- 4 files changed, 70 insertions(+), 57 deletions(-) diff --git a/index.html b/index.html index 4148d675..07fbb779 100644 --- a/index.html +++ b/index.html @@ -1085,9 +1085,6 @@
- @@ -1226,7 +1223,8 @@ -
+
+
Downloading update: diff --git a/js/ui.js b/js/ui.js index 5313150f..b79430bf 100644 --- a/js/ui.js +++ b/js/ui.js @@ -3,10 +3,6 @@ const startTime = performance.now(); let LABELS = [], DELETE_HISTORY = []; -// Mac Test: m2 macbook is reported as "MacIntel", I'd expect this to change at some point, hence regexp. - - - const STATE = { mode: 'analyse', openFiles: [], @@ -101,7 +97,7 @@ let fileList = [], analyseList = []; let fileStart, bufferStartTime, fileEnd; let zero = new Date(Date.UTC(0, 0, 0, 0, 0, 0)); -// set up some DOM element caches +// set up some DOM element hamdles const bodyElement = document.body; let specElement, waveElement, specCanvasElement, specWaveElement; let waveCanvasElement, waveWaveElement; @@ -1549,8 +1545,12 @@ window.onload = async () => { // check for new version on mac platform. pkg containers are not an auto-updatable target // https://www.electron.build/auto-update#auto-updatable-targets isMac && checkForMacUpdates(); - + const doNotTrack = document.getElementById('do-not-track') + doNotTrack.checked = !config.track; + if (config.track) { + const {width, height} = window.screen; fetch(`https://analytics.mattkirkland.co.uk/matomo.php?idsite=2&rand=${Date.now()}&rec=1&uid=${config.UUID}&apiv=1 + &res=${width}x${height} &dimension1=${config.model} &dimension2=${config.list} &dimension9=${JSON.stringify(config.detect)} @@ -1565,6 +1565,7 @@ window.onload = async () => { }) .catch(error => console.log('Error posting tracking:', error)) } + } ) // establish the message channel setUpWorkerMessaging() @@ -1600,14 +1601,15 @@ const setUpWorkerMessaging = () => { case "files": {onOpenFiles(args); break; } - case "generate-alert": {if (args.updateFilenamePanel) { + case "generate-alert": { + if (args.updateFilenamePanel) { renderFilenamePanel(); window.electron.unsavedRecords(false); document.getElementById("unsaved-icon").classList.add("d-none"); } if (args.file) { - let message = args.message; - alert(message); + //alert(message); + generateToast({domID:'toastContainer', message: args.message, type: 'warning'}); } else { if (args.filter) { worker.postMessage({ @@ -1622,7 +1624,8 @@ const setUpWorkerMessaging = () => { clearResults: true }); } else { - alert(args.message); + //alert(args.message); + generateToast({domID:'toastContainer', message: args.message, type: 'warning'}); } } break; @@ -2204,7 +2207,12 @@ function onChartData(args) { updateElementCache(); } - + const LIST_MAP = { + location: 'Searching for birds in your region', + nocturnal: 'Searching for nocturnal birds', + birds: 'Searching for all birds', + everything: 'Searching for everything' + }; // list mode icons const listIcon = document.getElementById('list-icon') const speciesThresholdEl = document.getElementById('species-threshold-el'); @@ -2212,29 +2220,17 @@ function onChartData(args) { const updateListIcon = () => { const icon = listIcon.querySelector('img'); icon.src = icon.src.replace(/\w+\.png$/, config.list + '.png'); - const states = { - location: 'Searching for birds in your region', - nocturnal: 'Searching for nocturnal birds', - birds: 'Searching for all birds', - everything: 'Searching for everything' - }; - icon.title = states[config.list]; + icon.title = LIST_MAP[config.list]; } listIcon.addEventListener('click', () => { let img = listIcon.querySelector('img') - const states = { - location: 'Searching for birds in your region', - nocturnal: 'Searching for nocturnal birds', - birds: 'Searching for all birds', - everything: 'Searching for everything' - }; - const keys = Object.keys(states); - for (let key in Object.keys(states)) { + const keys = Object.keys(LIST_MAP); + for (let key in Object.keys(LIST_MAP)) { key = parseInt(key); if (img.src.includes(keys[key])) { const replace = (key === keys.length - 1) ? 0 : key + 1; img.src = img.src.replace(keys[key], keys[replace]); - img.title = states[keys[replace]]; + img.title = LIST_MAP[keys[replace]]; DOM.listToUse.value = keys[replace]; config.list = keys[replace]; updatePrefs(); @@ -2994,7 +2990,8 @@ function onChartData(args) { } if (typeof (result) === 'string') { const nocturnal = config.detect.nocmig ? 'during the night' : ''; - tr += `
`; + generateToast({domID:'toastContainer', message: `${result}
${LIST_MAP[config.list]} detected ${nocturnal} with at least ${config.detect.confidence}% confidence in the prediction.`, type: 'warning'}); + //tr += ``; } else { const { timestamp, @@ -3413,17 +3410,13 @@ function onChartData(args) { worker.postMessage({ action: 'update-state', detect: { nocmig: config.detect.nocmig }, + globalOffset: 0, filteredOffset: {} }); updatePrefs(); - worker.postMessage({ action: 'update-state', globalOffset: 0, filteredOffset: {}}); - + if (currentFile){ resetResults({clearSummary: true, clearPagination: true, clearResults: false}); filterResults() - // worker.postMessage({ - // action: 'filter', - // species: isSpeciesViewFiltered(true), - // updateSummary: true - // }) + } } function filterResults({species = isSpeciesViewFiltered(true), updateSummary = true, offset = 0, limit = 500, range = undefined} = {}){ @@ -3487,10 +3480,6 @@ function onChartData(args) { }) DOM.nocmigButton.addEventListener('click', changeNocmigMode); - //DOM.nocmig.addEventListener('change', changeNocmigMode) - - //DOM.contextAware.addEventListener('change', toggleContextAwareMode) - const fullscreen = document.getElementById('fullscreen'); const toggleFullscreen = () => { @@ -4163,7 +4152,7 @@ DOM.gain.addEventListener('input', () => { LABELS = filecontents.trim().split(/\r?\n/); // Add unknown species LABELS.push('Unknown Sp._Unknown Sp.'); - worker.postMessage({action: 'update-locale', locale: config[config.model].locale, labels: LABELS}) + worker.postMessage({action: 'update-locale', locale: config[config.model].locale, labels: LABELS, refreshResults: !!currentFile}) }).catch(error =>{ console.error('There was a problem fetching the label file:', error); }) @@ -4279,10 +4268,12 @@ DOM.gain.addEventListener('input', () => { }) function track(event, action, name, value){ + if (config.track){ + const t = new Date() name = name ? `&e_n=${name}` : ''; value = value ? `&e_v=${value}` : ''; - if (config.track){ - fetch(`https://analytics.mattkirkland.co.uk/matomo.php?action_name=Settings%20Change&idsite=2&rand=${Date.now()}&rec=1&uid=${config.UUID}&apiv=1 + fetch(`https://analytics.mattkirkland.co.uk/matomo.php?h=${t.getHours()}&m=${t.getMinutes()}&s=${t.getSeconds()} + &action_name=Settings%20Change&idsite=2&rand=${Date.now()}&rec=1&uid=${config.UUID}&apiv=1 &e_c=${event}&e_a=${action}${name}${value}`) .then(response => { if (! response.ok) throw new Error('Network response was not ok', response); @@ -4630,7 +4621,7 @@ function track(event, action, name, value){ const current = parseSemVer(VERSION); if (isNewVersion(latest, current)) { - const alertPlaceholder = document.getElementById('liveAlertPlaceholder') + const alertPlaceholder = document.getElementById('updateAlert') const alert = (message, type) => { const wrapper = document.createElement('div') wrapper.innerHTML = [ @@ -4644,7 +4635,7 @@ function track(event, action, name, value){ alert(` There's a new version of Chirpity available! Check the website for more information`, - 'info') + 'warning') } config.lastUpdateCheck = latestCheck; updatePrefs() @@ -4655,6 +4646,32 @@ function track(event, action, name, value){ } } + function generateToast({domID, message}) { + const domEl = document.getElementById(domID); + + const wrapper = document.createElement('div'); + // Add toast attributes + wrapper.setAttribute("class", "toast"); + wrapper.setAttribute("role", "alert"); + wrapper.setAttribute("aria-live", "assertive"); + wrapper.setAttribute("aria-atomic", "true"); + + wrapper.innerHTML = ` +
+ + Notice + just now + +
+
+ ${message} +
` + + domEl.appendChild(wrapper) + const toast = new bootstrap.Toast(wrapper) + toast.show() + } + function parseSemVer(versionString) { const semVerRegex = /^[vV]?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z-.]+))?(?:\+([0-9A-Za-z-.]+))?$/; const matches = versionString.match(semVerRegex); diff --git a/js/worker.js b/js/worker.js index 129bae4c..66b111f3 100644 --- a/js/worker.js +++ b/js/worker.js @@ -388,7 +388,7 @@ case "update-list": { } case 'update-locale': { - await onUpdateLocale(args.locale, args.labels) + await onUpdateLocale(args.locale, args.labels, args.refreshResults) break; } case "update-state": { @@ -1953,8 +1953,7 @@ const prepSummaryStatement = (included) => { if (response.changes){ STATE.db === diskDB ? UI.postMessage({ event: 'diskDB-has-records' }) : UI.postMessage({event: 'unsaved-records'}); } - // let test = await db.allAsync('SELECT * from records where datetime = ?', dateTime) - // console.log('After insert: ',JSON.stringify(test)); + // WHY NOT USE FILTER DIRECTLY? UI.postMessage({ event: 'generate-alert', // message: `${count} ${args.cname} record has been saved to the archive.`, @@ -2178,7 +2177,7 @@ const prepSummaryStatement = (included) => { // Nothing to do for this file updateFilesBeingProcessed(file); - const result = `No detections in ${file}. It has no period within it where predictions would be given`; + const result = `No detections. The file has no period within it where predictions would be given. Tip: Disable nocmig mode.`; index++; UI.postMessage({ event: 'new-result', file: file, result: result, index: index @@ -2200,7 +2199,7 @@ const prepSummaryStatement = (included) => { } } } else { - DEBUG && console.log('Recursion: not found') + DEBUG && console.log('Recursion: file not found') await processNextFile(arguments[0]); } } @@ -2955,7 +2954,7 @@ const prepSummaryStatement = (included) => { } } - async function onUpdateLocale(locale, labels){ + async function onUpdateLocale(locale, labels, refreshResults){ let t0 = performance.now(); await diskDB.runAsync('BEGIN'); await memoryDB.runAsync('BEGIN'); @@ -2989,8 +2988,7 @@ const prepSummaryStatement = (included) => { await diskDB.runAsync('END'); await memoryDB.runAsync('END'); STATE.update({locale: locale}); - await getResults() - await getSummary(); + if (refreshResults) await Promise.all([getResults(), getSummary()]) } async function onSetCustomLocation({ lat, lon, place, files, db = STATE.db }) { diff --git a/package.json b/package.json index 2daeaa87..8686e79a 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "scripts": { "start": "electron .", "minify": "node minify.js", - "export": "electron-builder build --win --x64", + "export": "electron-builder build --m", "publish": "electron-builder --win --x64 -p always" }, "repository": { From 1cceb226ea8b3be9872ed93664ab4ff46c1c5f53 Mon Sep 17 00:00:00 2001 From: mattk70 Date: Sat, 17 Feb 2024 13:00:42 +0000 Subject: [PATCH 2/4] replaced remaining 'alerts' with toasts added STATE.analysisDone to indicate whether a file has been analysed, so as not to unnecessarily call "no detections' toast. added a text-nowrap to stop 'awaiting detectinos...' splitting over lines Fixed error in charts meaning no species displayed --- index.html | 5 -- js/ui.js | 129 +++++++++++++++++++-------------------------------- js/worker.js | 4 +- 3 files changed, 49 insertions(+), 89 deletions(-) diff --git a/index.html b/index.html index 07fbb779..eb613c64 100644 --- a/index.html +++ b/index.html @@ -211,11 +211,6 @@
Saved Records
Analysis
`; } else { const { @@ -3228,7 +3213,7 @@ function onChartData(args) { }) } else { if (!config.seenThanks) { - alert('Thank you, your feedback helps improve Chirpity predictions'); + generateToast({domID:'toastContainer', message:'Thank you, your feedback helps improve Chirpity predictions'}); config.seenThanks = true; updatePrefs() } @@ -3413,14 +3398,14 @@ function onChartData(args) { globalOffset: 0, filteredOffset: {} }); updatePrefs(); - if (currentFile){ + if (STATE.analysisDone){ resetResults({clearSummary: true, clearPagination: true, clearResults: false}); filterResults() } } function filterResults({species = isSpeciesViewFiltered(true), updateSummary = true, offset = 0, limit = 500, range = undefined} = {}){ - worker.postMessage({ + STATE.analysisDone && worker.postMessage({ action: 'filter', species: species, updateSummary: updateSummary, @@ -3580,10 +3565,6 @@ function onChartData(args) { worker.postMessage({ action: 'update-state', sortOrder: order }) resetResults({clearSummary: false, clearPagination: false, clearResults: true}); filterResults() - // worker.postMessage({ - // action: 'filter', - // species: isSpeciesViewFiltered(true) - // }) // re-prepare } // Drag file to app window to open @@ -3735,12 +3716,6 @@ function onChartData(args) { resetResults({clearSummary: true, clearPagination: true, clearResults: false}); worker.postMessage({ action: 'update-state', globalOffset: 0, filteredOffset: {}, explore: STATE.explore}); filterResults({range:STATE.explore.range}) - // worker.postMessage({ - // action: 'filter', - // species: isSpeciesViewFiltered(true), - // range: STATE.explore.range, - // updateSummary: true - // }); // re-prepare } // Update the seen species list @@ -3762,13 +3737,7 @@ function onChartData(args) { STATE.explore.range = {start: undefined, end: undefined}; worker.postMessage({ action: 'update-state', globalOffset: 0, filteredOffset: {}, explore: STATE.explore}); resetResults({clearSummary: true, clearPagination: true, clearResults: false}); - filterResults({species:STATE.explore.species, range:STATE.explore.range}) - // worker.postMessage({ - // action: 'filter', - // species: STATE.explore.species, - // range: STATE.explore.range, - // updateSummary: true - // }); // re-prepare + filterResults({species:STATE.explore.species, range:STATE.explore.range}); } }) picker.on('click', (e) =>{ @@ -3918,12 +3887,7 @@ function onChartData(args) { if (!PREDICTING && !DOM.resultTableElement.classList.contains('d-none')) { worker.postMessage({ action: 'update-state', globalOffset: 0, filteredOffset: {}}); resetResults({clearSummary: true, clearPagination: true, clearResults: false}); - filterResults() - // worker.postMessage({ - // action: 'filter', - // species: isSpeciesViewFiltered(true), - // updateSummary: true - // }); + filterResults(); } } @@ -4071,12 +4035,12 @@ DOM.gain.addEventListener('input', () => { case 'species-frequency-threshold' : { if (isNaN(element.value) || element.value === '') { - alert('The threshold must be a number between 0.001 and 1'); + generateToast({domID:'toastContainer', message:'The threshold must be a number between 0.001 and 1'}); return false } config.speciesThreshold = element.value; worker.postMessage({ action: 'update-state', speciesThreshold: element.value }); - worker.postMessage({ action: 'update-list', list: config.list, refreshResults: !!currentFile}); + worker.postMessage({ action: 'update-list', list: config.list, refreshResults: STATE.analysisDone}); break; } case 'timelineSetting': { @@ -4121,7 +4085,7 @@ DOM.gain.addEventListener('input', () => { if (! config.useWeek) STATE.week = -1; worker.postMessage({action:'update-state', useWeek: config.useWeek}); - worker.postMessage({ action: 'update-list', list: config.list, refreshResults: !!currentFile}); + worker.postMessage({ action: 'update-list', list: config.list, refreshResults: STATE.analysisDone}); break; } case 'list-to-use': { @@ -4138,7 +4102,7 @@ DOM.gain.addEventListener('input', () => { } updateListIcon(); resetResults({clearSummary: true, clearPagination: true, clearResults: true}); - worker.postMessage({ action: 'update-list', list: config.list, refreshResults: !!currentFile}); + worker.postMessage({ action: 'update-list', list: config.list, refreshResults: STATE.analysisDone}); break; } case 'locale': { @@ -4152,7 +4116,7 @@ DOM.gain.addEventListener('input', () => { LABELS = filecontents.trim().split(/\r?\n/); // Add unknown species LABELS.push('Unknown Sp._Unknown Sp.'); - worker.postMessage({action: 'update-locale', locale: config[config.model].locale, labels: LABELS, refreshResults: !!currentFile}) + worker.postMessage({action: 'update-locale', locale: config[config.model].locale, labels: LABELS, refreshResults: STATE.analysisDone}) }).catch(error =>{ console.error('There was a problem fetching the label file:', error); }) @@ -4188,6 +4152,7 @@ DOM.gain.addEventListener('input', () => { config.backend = 'tensorflow'; document.getElementById('tensorflow').checked = true; handleBackendChange(config.backend); + STATE.analysisDone = false; break; } case 'thread-slider': { @@ -4610,7 +4575,7 @@ function track(event, action, name, value){ function checkForMacUpdates() { // Do this at most daily - const latestCheck = Date.now() + const latestCheck = Date.now(); const checkDue = (latestCheck - config.lastUpdateCheck) > 86_400_000; if (checkDue){ fetch('https://api.github.com/repos/Mattk70/Chirpity-Electron/releases/latest') diff --git a/js/worker.js b/js/worker.js index 66b111f3..3e474f8b 100644 --- a/js/worker.js +++ b/js/worker.js @@ -2189,7 +2189,7 @@ const prepSummaryStatement = (included) => { if (!sumObjectValues(predictionsReceived)) { UI.postMessage({ event: 'progress', - text: "Awaiting detections", + text: "Awaiting detections", file: file }); } @@ -2721,7 +2721,7 @@ const prepSummaryStatement = (included) => { JOIN files on records.fileID = files.id`; if (STATE.mode === 'explore') sql += ` WHERE confidence >= ${confidence}`; - if (STATE.list !== 'location' && filtersApplied(included)) { + if (STATE.list !== 'location' && filtersApplied(STATE.included)) { sql += ` AND speciesID IN (${STATE.included.join(',')})`; } if (range?.start) sql += ` AND datetime BETWEEN ${range.start} AND ${range.end}`; From 2a52512d2da17e9f9c122dc7dc813fd19b1f1cea Mon Sep 17 00:00:00 2001 From: mattk70 Date: Sat, 17 Feb 2024 14:50:35 +0000 Subject: [PATCH 3/4] fixed a bug where clicking a species in the summary in explore mode had no effect. Added a check for active row in insert-manual-record --- js/ui.js | 2 +- js/worker.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/js/ui.js b/js/ui.js index f9f150ba..ab9d02b2 100644 --- a/js/ui.js +++ b/js/ui.js @@ -4439,7 +4439,7 @@ function track(event, action, name, value){ DBaction: action, batch: batch, confidence: confidence, - active: activeRow.rowIndex - 1 // have to account for the header row + active: activeRow?.rowIndex - 1 // have to account for the header row }) } diff --git a/js/worker.js b/js/worker.js index 3e474f8b..ae373d36 100644 --- a/js/worker.js +++ b/js/worker.js @@ -2397,6 +2397,7 @@ const prepSummaryStatement = (included) => { } else { sendResult(++index, r, true) } + if (i === result.length -1) UI.postMessage({event: 'processing-complete'}) } if (!result.length) { if (STATE.selection) { From 42f4e1ca270e52f8bc4f16711ce36c5611fd5f36 Mon Sep 17 00:00:00 2001 From: mattk70 Date: Sat, 17 Feb 2024 16:19:50 +0000 Subject: [PATCH 4/4] fixed all calls to onchangemode to await them removed promise all from retrieveFromDatabase --- js/worker.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/js/worker.js b/js/worker.js index ae373d36..6d0cbae5 100644 --- a/js/worker.js +++ b/js/worker.js @@ -271,7 +271,7 @@ async function handleMessage(e) { break; } case "change-mode": { - onChangeMode(args.mode); + await onChangeMode(args.mode); break; } case "chart": { @@ -305,7 +305,7 @@ async function handleMessage(e) { filesBeingProcessed.length && onAbort(args); DEBUG && console.log("Worker received audio " + args.file); await loadAudioFile(args); - metadata[args.file].isSaved ? onChangeMode("archive") : onChangeMode("analyse"); + metadata[args.file].isSaved ? await onChangeMode("archive") : await onChangeMode("analyse"); break; } case "filter": {if (STATE.db) { @@ -804,10 +804,12 @@ const prepSummaryStatement = (included) => { // handle circle here await getResults({ topRankin: 5 }); } else { - onChangeMode('archive'); - FILE_QUEUE.forEach(file => UI.postMessage({ event: 'update-audio-duration', value: metadata[file].duration })) - await Promise.all([getResults(), getSummary()] ); - + await onChangeMode('archive'); + FILE_QUEUE.forEach(file => UI.postMessage({ event: 'update-audio-duration', value: metadata[file].duration })); + // Wierdness with promise all - list worker called 2x and no results returned + //await Promise.all([getResults(), getSummary()] ); + await getResults(); + await getSummary(); } return; } @@ -2552,7 +2554,7 @@ const prepSummaryStatement = (included) => { if (!DATASET) { // Now we have saved the records, set state to DiskDB - onChangeMode('archive'); + await onChangeMode('archive'); getLocations({ db: STATE.db, file: file }); UI.postMessage({ event: 'generate-alert', @@ -2938,7 +2940,7 @@ const prepSummaryStatement = (included) => { const onFileDelete = async (fileName) => { const result = await diskDB.runAsync('DELETE FROM files WHERE name = ?', fileName); if (result.changes) { - onChangeMode('analyse'); + await onChangeMode('analyse'); getDetectedSpecies(); UI.postMessage({ event: 'generate-alert',
${result} (Showing ${config.list} detected ${nocturnal} with at least ${config.detect.confidence}% confidence in the prediction)
${result} (${LIST_MAP[config.list]} detected ${nocturnal} with at least ${config.detect.confidence}% confidence in the prediction)
${result} (${LIST_MAP[config.list]} detected ${nocturnal} with at least ${config.detect.confidence}% confidence in the prediction)