Skip to content

Instantly share code, notes, and snippets.

@daformat
Last active March 30, 2025 08:17
Show Gist options
  • Save daformat/7fd09f72784764a4b4be382b020b0c82 to your computer and use it in GitHub Desktop.
Save daformat/7fd09f72784764a4b4be382b020b0c82 to your computer and use it in GitHub Desktop.
Beep everytime a new row is added in instant db explorer views
/**
* ---------------------------------
* Instantdb new db entry monitoring
* ---------------------------------
* Chime every time a row is added to the table body, navigate to next page
* when available. Designed for instantdb explorer tables, with the $users
* table specifically in mind but works for any other table
*
* - go to the explorer page you want to watch
* - go to the last page available
* - open your console and paste this script (which you should review first for
* security reasons, **never** blindly paste code in a secure/authenticated
* context)
* - you should hear a first chime that confirms the script is running
* - the script will chime again anytime a new row is added to the table
*/
(function () {
let pendingChime = 0;
let dequeueTimeout = null;
// Create the audio context
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
/**
* Trigger a single beep
* @param options
* @param callback
*/
function beep(options, callback) {
options = options || {};
const volume = options.volume || 1;
const frequency = options.frequency || 196;
const type = options.type || "sine";
const duration = options.duration || 500;
const delay = options.delay || 0;
const harsh = options.harsh || false;
setTimeout(
function () {
const oscillator = audioCtx.createOscillator();
const gainNode = audioCtx.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioCtx.destination);
oscillator.frequency.value = frequency;
oscillator.type = type;
if (callback) {
oscillator.onended = callback;
}
if (harsh) {
gainNode.gain.value = volume;
} else {
gainNode.gain.value = 0.00001;
gainNode.gain.setTargetAtTime(
volume,
audioCtx.currentTime,
duration / 1000,
);
gainNode.gain.setTargetAtTime(
0.00001,
audioCtx.currentTime + duration / 4 / 1000,
duration / 4 / 1000,
);
}
oscillator.start();
setTimeout(function () {
oscillator.stop();
}, duration);
},
delay ? delay : 0,
);
}
/**
* Handle the table mutations
* @param mutations{MutationRecord[]}
*/
function handleMutation(mutations) {
for (const mutation of mutations) {
if (mutation.type === "childList" && mutation.addedNodes) {
const addedRows = [...mutation.addedNodes].filter(
(node) => node instanceof HTMLTableRowElement,
);
const nbNewRows = addedRows.length;
if (nbNewRows > 0) {
pendingChime += nbNewRows;
if (dequeueTimeout === null) {
dequeueTimeout = setTimeout(consumeChime, 750);
}
}
}
}
}
/**
* Compose beeps to produce a nice chime
*/
function chime() {
// 196 Hz is the frequency for a G3
// see https://www.seventhstring.com/resources/notefrequencies.html
const baseFreq = 196;
// 4 / 3 is the ratio for a perfect fourth, so it'll always sound nice
// see http://www.sengpielaudio.com/Root-Ratios.gif
beep({
frequency: baseFreq,
delay: 0,
});
beep({
frequency: (baseFreq * 4) / 3,
delay: 150,
});
beep({
frequency: (((baseFreq * 4) / 3) * 4) / 3,
delay: 150 * 2,
});
}
/**
* Dequeue beeps
*/
function consumeChime() {
if (pendingChime > 0) {
pendingChime -= 1;
chime();
if (pendingChime > 0) {
dequeueTimeout = setTimeout(consumeChime, 750);
} else {
dequeueTimeout = null;
}
}
}
// Get the html elements we're interested in
const table = document.querySelector("table");
const tableControls = document.querySelector("div:has(+ div > table)");
const prevNext = tableControls.querySelectorAll("button:has(>svg)");
const prevNextArray = Array.from(prevNext).filter(
(button) => button instanceof HTMLButtonElement,
);
// Listen to disabled attribute change on the next button to automatically
// click it when possible
const [, next] = prevNextArray;
if (prevNextArray.length !== 2) {
console.log(
"Prev / next buttons detection might be broken, got:",
prevNextArray,
"assumed next button is",
next,
);
}
if (next instanceof HTMLButtonElement) {
const handleClickNext = () => {
if (!next.disabled) {
next.click();
}
};
const observer = new MutationObserver(handleClickNext);
observer.observe(next, { attributes: true });
}
// Listen to the table mutations
if (table) {
lastNbRows = table.querySelectorAll("tbody > tr").length;
const observer = new MutationObserver(handleMutation);
observer.observe(table, { childList: true, subtree: true });
}
// chime once to confirm the script is installed
chime();
console.log('🔔 You will hear that sound every time a new row is added to the table')
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment