Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0f45414
add robohash avatar set to config options
mxew Oct 21, 2022
005fdaa
tweak trading card avatar set val name
mxew Oct 21, 2022
6982440
upversion
mxew Oct 21, 2022
97c5817
Change the twitter emoji CDN
cyancey76 Jan 9, 2023
ff27cda
Merge pull request #159 from cyancey76/twemoji-temp-fix
mxew Jan 9, 2023
270d35d
hello twemoji
mxew Jan 10, 2023
37d1c22
add newMusic (fresh produce) feed to ftapi
mxew Feb 16, 2023
3524194
tag edit thank you
mxew Jan 11, 2024
f702633
chris wants to be able to close the edit box
mxew Jan 11, 2024
ef7f1f7
remove old tag editor close button
mxew Jan 11, 2024
99740f2
produce on login screen
mxew Jan 11, 2024
aa5c969
kill produce on small screens for now
mxew Jan 11, 2024
8b292cd
make it so people in medium mode can see their queue again
mxew Jan 11, 2024
fda5412
discover lite on mobile thank you
mxew Jan 12, 2024
cb2a8cd
fix mobile chat bug
mxew Jan 12, 2024
a9dc7b6
add scrobble #28
mxew May 24, 2024
62c8807
last.fm: be less official thanks
tjwds May 25, 2024
d6bfe1e
Merge pull request #163 from tjwds/clean-for-scrobble
mxew May 25, 2024
615e999
also add tag clean to scrobble and love lastfm functions
mxew May 25, 2024
e866544
lights never go away
mxew Nov 30, 2024
25eb912
update the xmas lights
mxew Jan 3, 2025
22f0009
yt embed api needs a video id on init now
mxew Jun 26, 2025
6ad26ea
Merge remote-tracking branch 'upstream/master' into staging
andrewgtibbetts Apr 11, 2026
2ee46a0
today's work, mostly ui tweaks
andrewgtibbetts Apr 12, 2026
6b1fd04
Merge upstream PR #168: add pluralize utility and fix play/song/track…
andrewgtibbetts Apr 12, 2026
0d89e22
more tweaks, timeline recents, screen up background thing, fresh prod
andrewgtibbetts Apr 12, 2026
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
761 changes: 600 additions & 161 deletions css/looks.css

Large diffs are not rendered by default.

259 changes: 169 additions & 90 deletions index.html

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions js/chat.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ firetable.ui.setupChatEvents = function () {
// ── New message block (different user or @-mention break) ──
var $chatthing = $chatTemplate.clone();
$chatthing.attr('id', "chat" + chatData.chatID);
$chatthing.find('.botson').css(
$chatthing.find('.ft-avatar').css(
'background-image',
"url(" + firetable.utilities.avatarURL(chatData.id, namebo) + ")"
);
Expand All @@ -201,7 +201,7 @@ firetable.ui.setupChatEvents = function () {
$chatthing.find(".chatName").text(namebo);

// Click-to-@ on avatar and name
firetable.utilities.chatAt($chatthing.find('.botson'));
firetable.utilities.chatAt($chatthing.find('.ft-avatar'));
firetable.utilities.chatAt($chatthing.find('.chatName'));
twemoji.parse($chatthing.find(".chatText")[0]);
$chatthing.appendTo("#chats");
Expand Down
72 changes: 35 additions & 37 deletions js/firetable.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ var ftapi = {
started: false,
loggedIn: false,
banned: false,
isMod: false,
presenceDetectRef: null,
presenceDetectEvent: null,
blockEvent: null,
Expand Down Expand Up @@ -127,6 +128,21 @@ ftapi.init = function(firebaseConfig) {
ftapi.events.emit("newHistory", data);
});

recentz.on('child_changed', function(dataSnapshot) {
var data = dataSnapshot.val();
data.histID = dataSnapshot.key;
ftapi.events.emit("editedHistory", data);
});

// new songs emitter
var freshproduce = firebase.app("firetable").database().ref("newMusic");
freshproduce.on('child_added', function(dataSnapshot, prev) {
var data = dataSnapshot.val();
data.histID = dataSnapshot.key;
ftapi.events.emit("newProduce", data);
});


// table change emitter
var tbl = firebase.app("firetable").database().ref("table");
tbl.on('value', function(dataSnapshot) {
Expand Down Expand Up @@ -221,6 +237,10 @@ ftapi.init = function(firebaseConfig) {
firetable.avatarStyle = data.avatarStyle;
localStorage[STORAGE.avatarStyle] = data.avatarStyle;
}
if (data.mod || data.supermod) {
ftapi.isMod = true;
ftapi.events.emit("modCheck", true);
}
}

ftapi.events.emit("loggedIn", returnData);
Expand Down Expand Up @@ -516,42 +536,6 @@ ftapi.actions = {
var removeThis = ftapi.queueRef.child(trackID + "/flagged");
removeThis.remove();
},
editTrackTag: function(trackID, cid, newTag) {
if (ftapi.queue[trackID]) {
if (ftapi.queue[trackID].cid == cid) {
var changeref = ftapi.queueRef.child(trackID);
var trackObj = ftapi.queue[trackID];
trackObj.name = newTag;
changeref.set(trackObj);
} else {
//song appears to have moved since the editing began, let's try and find it...
for (var key in ftapi.queue) {
if (ftapi.queue.hasOwnProperty(key)) {
if (ftapi.queue[key].cid == cid) {
var changeref = ftapi.queueRef.child(key);
var trackObj = ftapi.queue[key];
trackObj.name = newTag;
changeref.set(trackObj);
return;
}
}
}
}
} else {
//song appears to have moved since the editing began, let's try and find it...
for (var key in ftapi.queue) {
if (ftapi.queue.hasOwnProperty(key)) {
if (ftapi.queue[key].cid == cid) {
var changeref = ftapi.queueRef.child(key);
var trackObj = ftapi.queue[key];
trackObj.name = newTag;
changeref.set(trackObj);
return;
}
}
}
}
},
deleteList: function(listID) {
var removeThis = firebase.app("firetable").database().ref("playlists/" + ftapi.uid + "/" + listID);
removeThis.remove();
Expand Down Expand Up @@ -790,6 +774,20 @@ ftapi.actions = {
deleteChat: function(feedID) {
var feedEntry = firebase.app("firetable").database().ref("chatFeed/" + feedID + "/hidden");
feedEntry.set(true);
},
editTag: function(type, cid, tag, histID){
console.log(type+" "+cid+" "+tag+ " "+histID);
var yargo = tag.split(" - ");
var theartist = yargo[0];
var thetitle = yargo[1];
//UPDATE HISTORY
var histRef = firebase.app("firetable").database().ref("songHistory/" + histID);
histRef.child("artist").set(theartist);
histRef.child("title").set(thetitle);
//UPDATE GLOBAL
var globalRef = firebase.app("firetable").database().ref("globalTracks/" + type + "" +cid);
globalRef.child("artist").set(theartist);
globalRef.child("title").set(thetitle);
}
};

Expand Down Expand Up @@ -1000,4 +998,4 @@ EventEmitter.prototype.once = function(event, listener) {
});
};

ftapi.ready();
ftapi.ready();
9 changes: 7 additions & 2 deletions js/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,11 @@ firetable.utilities = {
$('body').addClass('screen');
},

/** Returns "count singular" or "count plural" (defaults to singular+"s") */
pluralize: function (count, singular, plural) {
return count + " " + (count === 1 ? singular : (plural || singular + "s"));
},

// ─── Chat Scroll ─────────────────────────────────────────────────────────

/**
Expand Down Expand Up @@ -256,15 +261,15 @@ firetable.utilities = {

/**
* Attach a click handler to an element that inserts "@username " into the chat input.
* Works on .prson (user list), .botson (chat avatar), and .chatName elements.
* Works on .prson (user list), .ft-avatar (chat avatar), and .chatName elements.
* @param {jQuery} element - jQuery-wrapped DOM element
*/
chatAt: function (element) {
element.bind("click", function () {
var nameToAt;
if (element.hasClass("prson")) {
nameToAt = $(this).find(".prsnName").text();
} else if (element.hasClass("botson")) {
} else if (element.hasClass("ft-avatar")) {
nameToAt = $(this).next(".chatContent").find(".chatName").text();
} else if (element.hasClass("chatName")) {
nameToAt = $(this).text();
Expand Down
27 changes: 17 additions & 10 deletions js/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,25 @@ firetable.init = function () {
{ cls: "soundcloud", url: ftconfigs.soundcloudURL }
];
socialLinks.forEach(function (link) {
if (link.url) {
$(".sociallogo." + link.cls)
.attr("href", link.url)
.css("display", "inline-block");
}
if (link.url) $(".sociallogo." + link.cls).attr("href", link.url);
});

// Social popover: position anchored to trigger, swap icon on toggle
var socialPopoverEl = document.getElementById('socialPopover');
if (socialPopoverEl) {
socialPopoverEl.addEventListener('toggle', function (e) {
var btn = document.getElementById('socialTrigger');
var icon = btn && btn.querySelector('.material-icons');
if (e.newState === 'open') {
if (icon) icon.textContent = 'close';
socialPopoverEl.style.visibility = 'hidden';
firetable.ui.positionPopover(btn, socialPopoverEl, document.getElementById('socialArrow'));
} else {
if (icon) icon.textContent = 'share';
}
});
}

if (ftconfigs.logoImage) {
$("#roomlogo").css("background-image", "url(" + ftconfigs.logoImage + ")");
}
Expand All @@ -75,10 +87,6 @@ firetable.init = function () {
$('#playerArea, #scScreen')
.width($('#djStage').outerWidth())
.height($('#djStage').outerHeight());
$("#stealContain").css({
'top': $('#grab').offset().top + $('#grab').height(),
'left': $('#grab').offset().left - 16
});
setup(); // Re-create the p5.js canvas at the new size
}, 500));

Expand Down Expand Up @@ -117,7 +125,6 @@ firetable.init = function () {

// ── DOM Templates ──
$playlistItemTemplate = $('#mainqueue .pvbar').remove();
$tagEditorTemplate = $('.tagPromptBox').remove();

// ── Firebase Init ──
ftapi.init(ftconfigs.firebase);
Expand Down
76 changes: 60 additions & 16 deletions js/playlist.js
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ firetable.ui.dubtrackImportFileSelect = function (evt) {
});
}
if (firetable.dtImportList.length) {
$("#importDubResults").text("Ok... import " + firetable.dtImportName + " (" + firetable.dtImportList.length + " tracks)?");
$("#importDubResults").text("Ok... import " + firetable.dtImportName + " (" + firetable.utilities.pluralize(firetable.dtImportList.length, "track") + ")?");
$("#dubimportButton").show();
} else {
$("#importDubResults").text("ERROR... NO TRAX?");
Expand All @@ -372,13 +372,19 @@ firetable.ui.dubtrackImportFileSelect = function (evt) {
* @param {string} songid - data-key of the history item
* @param {string} tag - Current "Artist - Title" string
*/
firetable.actions.editTagsPrompt = function (songid, tag) {
var $pvbar = $('#thehistory .pvbar[data-key="' + songid + '"]');
$('#thehistory .pvbar.editing').removeClass('editing');
$('.tagPromptBox').remove();
firetable.actions.editTagsPrompt = function (songid, tag, anchorEl) {
var popoverEl = document.getElementById('tagEditorPopover');
if (popoverEl.matches(':popover-open')) popoverEl.hidePopover();
$('.pvbar.editing').removeClass('editing');
var $pvbar = $('.pvbar[data-key="' + songid + '"]').first();
$pvbar.addClass('editing');
var $tags = $tagEditorTemplate.clone().appendTo($pvbar);
$tags.find(".tagMachine").val(tag);
firetable.editingPvbar = $pvbar;
$(popoverEl).find('.tagMachine').val(tag);
popoverEl.style.visibility = 'hidden';
popoverEl.showPopover();
firetable.ui.positionPopover(anchorEl || $pvbar.find('.edittags')[0], popoverEl, document.getElementById('tagEditorArrow'), 'bottom').then(function () {
$(popoverEl).find('.tagMachine')[0].focus();
});
firetable.debug && console.log('edit tags song id:', songid);
};

Expand Down Expand Up @@ -417,6 +423,12 @@ firetable.ui.setupPlaylistEvents = function () {
.attr("data-type", thisone.type)
.attr("data-cid", thisone.cid);

// Album art thumbnail
var artUrl = thisone.type === MEDIA_YOUTUBE
? 'https://i.ytimg.com/vi/' + thisone.cid + '/mqdefault.jpg'
: '';
if (artUrl) $newli.find('.q-art').css('background-image', 'url(' + artUrl + ')');

// Preview button
$newli.find('.previewicon').attr('id', "pv" + key).on('click', function () {
firetable.actions.pview(
Expand Down Expand Up @@ -474,6 +486,28 @@ firetable.ui.setupPlaylistEvents = function () {
firetable.actions.deleteSong($(this).closest('.pvbar').attr('data-key'));
});

// Edit tags button
$newli.find('.edittags').on('click', function () {
var popoverEl = document.getElementById('tagEditorPopover');
var $pvbar = $(this).closest('.pvbar');
if (popoverEl.matches(':popover-open') && firetable.editingPvbar && firetable.editingPvbar.is($pvbar)) {
popoverEl.hidePopover();
} else {
firetable.actions.editTagsPrompt(
$pvbar.attr('data-key'),
$pvbar.find('.listwords').text(),
this
);
}
});

// Close editor button
$newli.find('.closeeditor').on('click', function () {
document.getElementById('tagEditorPopover').hidePopover();
});

if (!ftapi.isMod) $newli.find('.edittags, .closeeditor').hide();

// Add-to-playlist button
$newli.find('.histeal').on('click', function () {
var $btn = $(this);
Expand Down Expand Up @@ -507,10 +541,10 @@ firetable.ui.setupPlaylistEvents = function () {
firetable.stealSourceBtn = $btn;
firetable.stealTarget = { cid: btnCid, type: btnType, title: btnTitle };
$btn.addClass('on');
$("#stealContain").css({
top: $btn.offset().top + $btn.outerHeight(),
left: $btn.offset().left - 16
}).show();
var stealContainEl = document.getElementById('stealContain');
stealContainEl.style.visibility = 'hidden';
$("#stealContain").show();
firetable.ui.positionPopover($btn[0], stealContainEl, document.getElementById('stealArrow'), 'left');
});
});

Expand Down Expand Up @@ -641,19 +675,29 @@ firetable.ui.setupPlaylistEvents = function () {
// ── Tag editing (Enter in .tagMachine) ──
$(document).on("keyup", ".tagMachine", function (e) {
if (e.which !== 13) return;
var songCid = $(this).closest('.pvbar').attr('data-cid');
var histID = $(this).closest('.pvbar').attr('data-histid');
var $pvbar = firetable.editingPvbar;
if (!$pvbar || !$pvbar.length) return;
var val = $(this).val();
if (!val) return;
var yargo = val.split(" - ");
if (!yargo[0] || !yargo[1]) {
alert("check yr tags");
} else {
ftapi.actions.editTag(
$(this).closest('.pvbar').attr('data-type'),
songCid, val, histID
$pvbar.attr('data-type'),
$pvbar.attr('data-cid'),
val,
$pvbar.attr('data-histid')
);
$(this).closest('.editing').removeClass('editing').next('.tagPromptBox').remove();
document.getElementById('tagEditorPopover').hidePopover();
}
});

// ── Tag editor popover cleanup on auto-dismiss ──
document.getElementById('tagEditorPopover').addEventListener('toggle', function (e) {
if (e.newState === 'closed' && firetable.editingPvbar) {
firetable.editingPvbar.removeClass('editing');
firetable.editingPvbar = null;
}
});
};
35 changes: 35 additions & 0 deletions js/popover.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// ─── Floating UI Popover Positioning ────────────────────────────────────────
// Shared utility: positions a floating element anchored to a reference element.
// arrowEl is optional (pass null for no arrow).

firetable.ui.positionPopover = function (anchorEl, floatingEl, arrowEl) {
var middleware = [
FloatingUIDOM.offset(8),
FloatingUIDOM.flip(),
FloatingUIDOM.shift({ padding: 8 })
];
if (arrowEl) {
middleware.push(FloatingUIDOM.arrow({ element: arrowEl }));
}
FloatingUIDOM.computePosition(anchorEl, floatingEl, {
placement: 'bottom-start',
strategy: 'fixed',
middleware: middleware
}).then(function (pos) {
floatingEl.style.left = pos.x + 'px';
floatingEl.style.top = pos.y + 'px';

if (arrowEl && pos.middlewareData.arrow) {
var ax = pos.middlewareData.arrow.x;
var ay = pos.middlewareData.arrow.y;
var staticSide = { top: 'bottom', bottom: 'top', left: 'right', right: 'left' }[pos.placement.split('-')[0]];
Object.assign(arrowEl.style, {
left: ax != null ? ax + 'px' : '',
top: ay != null ? ay + 'px' : '',
right: '',
bottom: '',
[staticSide]: '-4px'
});
}
});
};
Loading