Created
April 28, 2019 18:25
-
-
Save hinell/c56f2c5a838321af0712d7bf1e962fb2 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/******************** | |
Name : SC.Tracks snippet | |
Version : 0.1.2 | |
Last-Modified : 18.04.19 | |
Description : | |
The programm walks over tracks: | |
Tracks.nodes = [ | |
1track | |
2track <---- tracks.current | |
3track | |
4track | |
5track | |
] | |
History: | |
0.1.2 | |
* Added confirmation dialog when accidentally clicking relocation | |
**********************/ | |
text = [ | |
`irritated` | |
, `centering` | |
, `Storm Thoughts` | |
, `Mattias spelar modulsynt VIII` | |
, `Dave Smith (Sequential) OB6` | |
, `fire in the cathedral` | |
// , `Aeternum I` | |
// , `SB410` | |
// , `The Outer Edge` | |
// , `Luullahan jotta on lysti olla` | |
// , `Evolutions` | |
// , `Distillery Road` | |
// , `Mildred` | |
// , `Lauvene - Infinity` | |
// , `Soul Elemental` | |
// , `Bones - ChangeOfScenery` | |
// , `Keeping On` | |
// , `Break Through` | |
// , `Where The River Flows` | |
// , `Boson Spin - Ambient Bite 390` | |
// , `Ragnarok` | |
// , `Subliminal` | |
// , `Vertrautheit` | |
// , `Par collision d'étoiles` | |
// , `Trio Initiate Disquiet0367` | |
// , `Going Forward While Looking Back` | |
// , `L12` | |
// , `Another Carefree Day On The Nostromo` | |
// , `Boson Spin - Ambient Bite 387` | |
] | |
console.clear() | |
// Utils | |
HTMLElement.prototype.$ = function(){return this.querySelector.apply(this,arguments) }; | |
HTMLElement.prototype.$$= function(){return this.querySelectorAll.apply(this,arguments)}; | |
String.prototype.contains = function(str){ return new RegExp(str).test(this) } | |
Array.prototype.forEach = | |
Array.prototype.each = function (fn ,this_){ | |
if(this.length <=0 || fn == undefined) return; | |
let i = 0 | |
if (this_) fn = fn.bind(this_); | |
while (i < this.length) { | |
fn(this[i],i++) | |
} | |
}; | |
Set.prototype.toArray = function(){ | |
let arr = [], iterator = this.values(), current; | |
while(!(current = iterator.next() ).done) arr.push(current.value) ; | |
return arr | |
}; | |
Error.prototype.throw = function(){ | |
throw this | |
} | |
NodeList.prototype.addEventListener = function (name, handler){ | |
[].slice.call(this).forEach(el => el.addEventListener(name, handler)) | |
} | |
NodeList.prototype.removeEventListener = function (name, handler){ | |
[].slice.call(this).forEach(el => el.removeEventListener(name, handler)) | |
} | |
var NodeListX = class { | |
constructor(nodes){ | |
this.nodes = [].slice.call(nodes); | |
} | |
filter(fn, that){ return new NodeListX(this.nodes.filter(fn, that)) } | |
addEventListener(name, handler){ | |
if (!this.nodes.length) { return } | |
this.nodes.forEach(el => el.addEventListener(name, handler)) | |
} | |
removeEventListener(name, handler){ | |
if (!this.nodes.length) { return } | |
this.nodes.forEach(el => el.removeEventListener(name, handler)) | |
} | |
concat(nodelistx) { | |
return new NodeListX(this.nodes.concat(nodelistx.nodes)) | |
} | |
} | |
// Major classes | |
// Class which runs certain function "fn" at fixed interval of time | |
var Observer = function(fn,frequency){ | |
this.fn = fn | |
this.fr = frequency | |
} | |
Observer.prototype.observe = | |
Observer.prototype.start = function (){ | |
if(this.interval || this.running) {return}; | |
this.running = true; | |
this.interval = setInterval(function(){ | |
this.fn() | |
}.bind(this),this.fr) | |
return this | |
} | |
Observer.prototype.disconnect = | |
Observer.prototype.stop = | |
Observer.prototype.cancel = function(){ | |
if(this.running && this.interval) { | |
clearInterval(this.interval); | |
return this.running = this.interval = false; | |
} | |
return this.running | |
} | |
var Button = class { | |
constructor (tag, id, style) { | |
this.el = document.getElementById(id) || document.createElement(tag || 'span') | |
this.el.id = id; | |
this.el.style = style || ''; | |
this.defaultStyle = style; | |
} | |
insertInto (el){ el.appendChild(el) } | |
listen(event,fn){ this.el.addEventListener(event, fn) } | |
label(str) { this.el.innerText = str || ''} | |
style(st){ | |
this.el.style = this.defaultStyle+st; | |
} | |
} | |
var SCButton = Button; | |
var Waveforms = function(selector){ this.selector = selector } | |
Waveforms.prototype.hide = function(){ | |
if (this.hidden) {return} | |
this.hidden = true | |
style = document.querySelector('style').sheet; | |
style.insertRule((this.selector || '.waveform')+' {display: none}',style.cssRules.length) | |
} | |
var Track = class { | |
constructor(e){ | |
this.e = e | |
this.mouseClickEvent = new MouseEvent('click', {bubble: true, cancelable: true, composed: false}) | |
} | |
play(){ this.toggle(); return this } | |
toggle(){ | |
this.e.querySelector(Track.playClassName) | |
.dispatchEvent(this.mouseClickEvent) | |
return this | |
} | |
} | |
Track.playClassName = '.sound__header .sc-button-play'; | |
// Events listeners: | |
// tracks.onRunning | |
// tracks.onStop | |
// tracks.onBad | |
// tracks.onFound | |
// tracks.onFetch | |
var Tracks = function(el,waveformselector,opt){ | |
this.update(el); | |
this.current = this.nodes[0]; | |
this.currentIndex = 0; // current checked node index of the tracks | |
// this.maxnodes = opt.nodeslimit == void 0 ? 127 : opt.nodeslimit // maximum nodes to load & to enumerate over | |
this.maxnodes = opt.nodeslimit; | |
this.direction = 'wn' // searching direction: up or wn (down) | |
this.waveforms = new Waveforms(waveformselector); | |
// Tracks removal config | |
this.remove = opt.nodeRemove == void 0 ? false : opt.nodeRemove | |
this.hide = opt.nodeHide == void 0 ? true : opt.nodeHide | |
this.tracksLImit = opt.tracksLimit || 200; | |
// Removing extr tracks | |
this.removedTracks= 0; | |
this.extraTrackRemoveObject = new Observer(function(){ | |
itemsCount = this.maxTracks; | |
if (this.tracksLImit | |
&& this.nodes.length > this.tracksLImit | |
&& this.currentIndex > this.tracksLImit - 1 | |
) { | |
let tracksToRemove = []; | |
tracksToRemove = [].slice.call(tracks.nodes, 0, this.tracksLImit) | |
tracksToRemove.forEach(e => { | |
e.parentElement.removeChild(e) | |
}); | |
this.removedTracks += tracksToRemove.length; | |
console.log(`Total tracks removed: %d`, this.removedTracks); | |
} | |
}.bind(this), 2000) | |
// fetching triggers soundcloud tracks dowload | |
this.fetching = new Observer(function(){ | |
// console.clear() | |
// console.log('FETCHING IN <= NODES',this.fetchedtimes,this.nodes.length) | |
// console.log(this.current) | |
this.fetchedtimes ? this.fetchedtimes++ : (this.fetchedtimes = 1); | |
this.maxnodes && this.fetchedtimes >= (this.maxnodes * 5) && this.fetching.cancel() | |
this.nodes.length >= this.maxnodes && this.fetching.cancel(); | |
this.current.nextElementSibling || this._fetch() | |
// play last track everytime we get the tracks feed | |
/*let t = this.nodes[this.nodes.length - 1]; | |
if(t) { | |
new Track(t).toggle() | |
}*/ | |
// this._fetch() | |
}.bind(this),100*4) | |
// Most important part. | |
// Checking method iterates over fetched tracks | |
// and checks match for the track we have provided by Tracks.find() method | |
this.checking = new Observer(this.check = function(){ | |
this._match() | |
if(this.found) { | |
// once found let's start to play | |
/* | |
try { | |
new Track(this.current).toggle() | |
} catch (e){ | |
console.log('Whooops. Play is failed!', e) | |
} | |
*/ | |
this.onFound && this.onFound(); | |
console.clear() | |
console.log('IT IS FOUND :) ',this.nodes.length); | |
console.log(this.current) | |
this.checking.cancel(); | |
this.fetching.cancel() | |
return | |
} | |
// if(this._hasNext()) return this._walk(); | |
if(this._hasNext()) { | |
this.onRunning && this.onRunning(this); | |
this._walk(); | |
} | |
// else { this.direction == 'wn' && this.nodes.length < this.maxnodes && this._fetch()}; | |
// FINALLY: NOTHING FOUND | |
if(!this._hasNext() && this.nodes.length >= this.maxnodes){ | |
console.clear() | |
console.log('NOTHING FOUND :( )',this.nodes.length) | |
this.onBad && this.onBad(this); | |
window.scroll(0,0) | |
this.checking.cancel(); | |
this.fetching.cancel() | |
return | |
} | |
}.bind(this),100*1.5); | |
// MAIN LOOOP with observers | |
this.observers = [ | |
this.checking | |
, this.fetching | |
, this.extraTrackRemoveObject | |
] | |
} | |
// Resets nodes to the specified element's children | |
Tracks.prototype.update = function(el){ | |
this.el = el; | |
if(!el) { | |
this.nodes = []; | |
return | |
} | |
this.className = '.'+el.className.split(/\s/g).join('.') | |
this.nodes = el.children; | |
} | |
Tracks.prototype._fetch = function(limit){ | |
let lastPosition = window.scrollY; | |
window.scroll(0,9999999) | |
// window.scroll(0,lastPosition); | |
this.onFetch && setTimeout(this.onFetch, 1000); | |
} | |
Tracks.prototype._match = function(selector){ | |
this.remove && (this.checked = this.checked == void 0 ? 0 : ++this.checked) | |
let textelem = this.current.querySelector(this.selector || selector); | |
textelem || new Error('Invalid selector: the target for string search hasn\'t been found! ').throw() | |
if(this.str instanceof Array) { | |
return this.found = this.str.some(searchString => new RegExp(searchString).test(textelem.innerText) ) | |
} | |
this.found = textelem.innerText.contains(this.str) | |
} | |
// True if a sibling of the current node exists | |
Tracks.prototype._hasNext= function(){ | |
switch (this.direction){ | |
case 'up': return this.current.previousElementSibling; | |
case 'wn': return this.current.nextElementSibling; | |
} | |
} | |
// Walking next | |
Tracks.prototype._walk = function(){ | |
let previous; | |
this.current.style.display == 'none' && (this.current.style.display = '') | |
switch (this.direction){ | |
case 'up': this.current = this.current.previousElementSibling; break; | |
case 'wn': | |
if(this.remove) { | |
previous = this.current; | |
this.current = this.current.nextElementSibling; | |
previous.parentElement.removeChild(previous) | |
} | |
if(this.hide) { | |
this.current.style.display = 'none'; | |
this.current = this.current.nextElementSibling; | |
} | |
if(!this.remove && !this.hide) { | |
this.current.style.border = 'none' | |
this.current.removeAttribute('style') | |
this.current = this.current.nextElementSibling | |
this.current.style.border = 'inset .6em #ff5500'; | |
} | |
} | |
this.currentIndex = [].slice.call(this.nodes).indexOf(this.current); | |
} | |
Tracks.prototype.find = function(searchstring,selector,direction = 'wn'){ | |
this.str = searchstring || this.str || new Error('Invalid argument: string required!').throw() | |
this.selector = selector || this.selector || new Error('Invalid argument: selector for string search required!').throw() | |
this.direction= direction || this.direction|| 'wn'; | |
console.log('SEARCHING ',this.direction == 'wn' ? 'DOWN' : 'UP') | |
} | |
Tracks.prototype.pause = | |
Tracks.prototype.stop = function(){ | |
if(!this.el) { return } | |
this.running = false | |
this.observers.forEach(e => e.stop() ); | |
this.onStop && this.onStop(this); | |
} | |
Tracks.prototype.start = function(){ | |
if(!this.el){ | |
this.running = false; | |
return | |
} | |
if(this.nodes.length !== this.el.children.length){ | |
this.pause() | |
this.reset() | |
} | |
this.running = true | |
this.observers.forEach(e => e.start() ); | |
} | |
Tracks.prototype.toggle = function(){ | |
if(!this.el) { return } | |
this.observers.every(o => o.running) | |
? this.pause() | |
: this.start() | |
} | |
// RESET SEARCH. Tip: uset it only after pausing | |
Tracks.prototype.reset = function(){ | |
this.current = this.nodes[this.currentIndex = 0]; | |
} | |
Tracks.prototype.show = function(){ | |
[].slice.call(this.nodes).each(e => e.style.display = '') | |
} | |
if(!!tracks) { tracks.pause(); tracks = undefined } | |
var tracks, | |
// RESET & STOP BUTTON | |
resetButton, stopButton; | |
resetButton = new SCButton('span', 'snippetResetButtonTrack', 'cursor: pointer'); | |
resetButton.el.innerText = "R"; | |
resetButton.el.title = "Reset button. Press to reset search to 0"; | |
resetButton.listen('click', () => { | |
tracks.pause(); | |
tracks.reset(); | |
stopButton.label('PROCEED: ' + tracks.currentIndex); | |
stopButton.el.style = ''; | |
}) | |
stopButton = new SCButton('span', 'trackFinderStopButton', 'width: 9em; cursor: pointer'); | |
stopButton.el.innerText = 'START SEARCH'; | |
stopButton.el.onclick = () => tracks.toggle(); | |
stopButton.el.title = 'Start/Pause button' | |
nextButton = new SCButton('span', 'trackPassOverButton', 'cursor: pointer') | |
nextButton.el.innerText = 'N'; | |
nextButton.el.title = 'Proceed to the next track'; | |
nextButton.el.onclick = function(){ | |
let next = tracks.current.nextElementSibling; | |
if(!next) { return } | |
tracks.current = next; | |
tracks.start(); | |
} | |
currentTrackButton = new SCButton('span', 'snippetShowCurrentTruck', 'cursor: pointer;' ) | |
currentTrackButton.el.innerText = 'CR'; | |
currentTrackButton.el.title = 'Scroll current track into view' | |
currentTrackButton.el.onclick = function(){ | |
tracks.current.scrollIntoView(); | |
tracks.current.style.border = 'inset .6em #ff5500'; | |
setTimeout(() => tracks.current.style.border = '', 5000 ) | |
}; | |
[resetButton, stopButton, nextButton, currentTrackButton] | |
.forEach(b => b.el.className = 'header__link header__proUpsell'); | |
// Program which prevents from following links and shows | |
// Confirmation dialog | |
// No arguments | |
var allLinks = new NodeListX(allLinks || []); | |
var confirmRelocationInit = function(){ | |
let confirmRelocation; | |
allLinks.removeEventListener(`click`, confirmRelocation); | |
let links = new NodeListX(document.querySelectorAll(`a`)); | |
links = links.filter((el) => { | |
const excludePattern = /Play|Pause|Stop|Continue|Repeat/g; | |
return !(excludePattern.test(el.title) || el.classList.contains(`compactTrackList__moreLink`) ) | |
}); | |
confirmRelocation = function (e) { | |
e.stopPropagation() | |
dialogIsopen = true; | |
const el = e.currentTarget; | |
const msg = ` | |
You are currently in search mode. Are you sure you want to go to ${el && el.href}? | |
(press cancel to stay here)`; | |
const allowedToRelocate = confirm(msg); | |
if (!allowedToRelocate) { | |
e.preventDefault(); | |
} | |
}; | |
links.addEventListener(`click`, confirmRelocation); | |
allLinks = links; | |
}; | |
var tracksClassName = '.lazyLoadingList__list.sc-list-nostyle.sc-clearfix'; | |
var initTrackSearch = function(){ | |
// Guard from accident relocation | |
document.querySelector('div.header__middle').style='display: none'; | |
var panel = document.querySelector('.'+'header__right sc-clearfix'.split(' ').join('.')) | |
// INITIALIZING BUTTONS | |
var buttons = [nextButton, resetButton, stopButton, currentTrackButton]; | |
buttons.reverse().forEach(function(button){ | |
let buttonEl = document.getElementById(button.el.id) | |
if(!buttonEl) { | |
panel.insertAdjacentElement('afterbegin', button.el); | |
} | |
}) | |
// resetButton.el.style = stopButton.el.style = ''; // display | |
if(document.location.pathname === '/stream'){ | |
var tracklist = document.querySelector(tracksClassName); | |
if(tracks) { | |
tracks.update(tracklist) | |
} else { | |
tracks = new Tracks(tracklist,'.waveform',{ | |
nodeslimit : 100*10 | |
,nodeHide : false // Hide nodes | |
}); | |
tracks.waveforms.hide(); | |
} | |
tracks.onRunning = function(){ | |
stopButton.label('...' + tracks.currentIndex); | |
stopButton.style('cursor: wait') | |
} | |
tracks.onBad = function(tracks){ | |
stopButton.label('BAD :('+tracks.currentIndex) | |
} | |
tracks.onFound = function(){ | |
stopButton.label(`SUCCESS ${this.currentIndex}:`); | |
var removeHighlight = function(){ | |
this.current.style.border = '' | |
} | |
// Highlight found node | |
currentTrackButton.el.onclick() | |
this.current.addEventListener('mouseover', removeHighlight) | |
setTimeout(function(){ | |
this.current.removeEventListener('mouseover', removeHighlight) | |
}.bind(this)) | |
// window.scroll(0,this.foundnode.offsetTop) | |
// window.scroll(0,this.current.getClientRects()[0].top+window.pageYOffset); | |
} | |
tracks.onStop = function(){ | |
stopButton.label('RESTART: ' + tracks.currentIndex); | |
stopButton.style('') | |
} | |
tracks.onFetch = confirmRelocationInit; | |
confirmRelocationInit(); | |
textsearchselector = '.soundTitle__usernameTitleContainer' | |
// text | |
;tracks.find(text,textsearchselector,'wn') | |
// ;tracks.toggle() | |
} else { | |
; | |
(resetButtonOk) && (resetButton.el.style = 'display: none'); // hide | |
(stopButtonOk) && (stopButton.el.style = 'display: none'); | |
tracks && tracks.update(undefined); | |
document.querySelector('div.header__middle').style='display:'; | |
} | |
} | |
if (window.location.pathname !== `/stream`) { | |
const stream = `https://soundcloud.com/stream`; | |
window.location.assign(stream) | |
alert(`You are about to go to the right page.\n Don't forget to run scripts!`) | |
} else { | |
var tracksFindingInterval; | |
if(tracksFindingInterval){ | |
console.log('Elements update interval cleared!') | |
clearInterval(tracksFindingInterval) | |
}; | |
tracksFindingInterval = setInterval(initTrackSearch, 5000); | |
initTrackSearch(); | |
;test = function(str, str2){ return new RegExp(str).test(str2 || str) }; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment