VulpesOS/gaia/apps/music/js/view.js
OverSu b38dea0597 Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
2026-09-30 23:13:03 +02:00

249 lines
6.6 KiB
JavaScript
Executable file

/* exported View */
/* global SERVICE_WORKERS, bridge */
window.View = (function() {
'use strict';
var debug = 0 ? (...args) => console.log('[View]', ...args) : () => {};
if (!SERVICE_WORKERS) (function() {
window.ROUTES = {
'/api/activities/share/:filePath': 'share',
'/api/albums/list': 'getAlbums',
'/api/albums/info/:filePath': 'getAlbum',
'/api/artists/list': 'getArtists',
'/api/artists/info/:filePath': 'getArtist',
'/api/artwork/original/:filePath': 'getSongArtwork',
'/api/artwork/thumbnail/:filePath': 'getSongThumbnail',
'/api/artwork/url/original/:filePath': 'getSongArtworkURL',
'/api/artwork/url/thumbnail/:filePath': 'getSongThumbnailURL',
'/api/audio/play': 'play',
'/api/audio/pause': 'pause',
'/api/audio/seek/:time': 'seek',
'/api/audio/fastseek/start/:direction': 'startFastSeek',
'/api/audio/fastseek/stop': 'stopFastSeek',
'/api/audio/status': 'getPlaybackStatus',
'/api/database/status': 'getDatabaseStatus',
'/api/playlists/list': 'getPlaylists',
'/api/playlists/info/:id': 'getPlaylist',
'/api/queue/current': 'currentSong',
'/api/queue/previous': 'previousSong',
'/api/queue/next': 'nextSong',
'/api/queue/album/:filePath': 'queueAlbum',
'/api/queue/artist/:filePath': 'queueArtist',
'/api/queue/playlist/:id/shuffle': 'queuePlaylist',
'/api/queue/playlist/:id/song/:filePath': 'queuePlaylist',
'/api/queue/song/:filePath': 'queueSong',
'/api/queue/repeat/:repeat': 'setRepeatSetting',
'/api/queue/shuffle/:shuffle': 'setShuffleSetting',
'/api/songs/list': 'getSongs',
'/api/songs/count': 'getSongCount',
'/api/songs/info/:filePath': 'getSong',
'/api/songs/rating/:rating/:filePath': 'setSongRating',
'/api/search/:key/': 'search',
'/api/search/:key/:query': 'search'
};
for (var path in window.ROUTES) {
var method = window.ROUTES[path];
window.ROUTES[path] = parseSimplePath(path);
window.ROUTES[path].method = method;
}
})();
function View() {
this.client = bridge.client({
service: 'music-service',
endpoint: window.parent,
timeout: false
});
this.params = {};
var parts = window.parent.location.href.split('?');
parts.shift();
var query = parts.join('?');
query.split('&').forEach((param) => {
var parts = param.split('=');
this.params[parts[0]] = parts[1];
});
window.addEventListener('click', (evt) => {
var link = evt.target.closest('a');
if (link) {
evt.preventDefault();
this.client.method('navigate', link.getAttribute('href'));
}
});
window.addEventListener('viewdestroy', () => this.destroy());
document.addEventListener('DOMRetranslated', () => {
this.title = document.title;
});
}
View.prototype.setupSearch = function(params) {
this.searchBox.addEventListener('search', (evt) => this.search(evt.detail));
this.searchResults.addEventListener('open', () => {
this.client.method('searchOpen');
});
this.searchResults.addEventListener('close', () => {
this.client.method('searchClose');
this.list.scrollTop = this.searchBox.HEIGHT;
});
this.searchResults.addEventListener('resultclick', (evt) => {
var link = evt.detail;
if (link) {
this.client.method('navigate', link.getAttribute('href'));
}
});
this.searchResults.getItemImageSrc = (item) => this.getThumbnail(item.name);
};
View.prototype.setupList = function() {
this.list.minScrollHeight = `calc(100% + ${this.searchBox.HEIGHT}px)`;
this.list.offset = this.searchBox.HEIGHT;
this.list.configure({
getItemImageSrc: (item) => this.getThumbnail(item.name)
});
// Show the view only when list has something
// rendered, this prevents Gecko painting unnecessarily.
this.list.rendered.then(() => document.body.hidden = false);
this.list.scrollTo(this.searchBox.HEIGHT);
};
View.prototype.destroy = function() {
Object.getOwnPropertyNames(this).forEach(prop => this[prop] = null);
debug('Destroyed');
};
View.prototype.render = function() {
// No-op in the base class.
};
View.prototype.onRenderDone = function() {
if (window.frameElement) {
window.frameElement.dispatchEvent(new CustomEvent('rendered'));
}
debug('Rendered');
};
View.prototype.fetch = SERVICE_WORKERS ?
function(url) {
return window.fetch(encodeURI(url));
} :
function(url) {
for (var path in window.ROUTES) {
var route = window.ROUTES[path];
var match = url.match(route.regexp);
if (match) {
return new Promise((resolve) => {
setTimeout(() => {
var args = [route.method].concat(match.splice(1));
this.client.method.apply(this.client, args).then((result) => {
resolve({
blob: () => Promise.resolve(result),
json: () => Promise.resolve(result)
});
});
});
});
}
}
return Promise.reject();
};
Object.defineProperty(View.prototype, 'title', {
get: function() {
return document.title;
},
set: function(value) {
document.title = value;
window.frameElement.dispatchEvent(new CustomEvent('titlechange', {
detail: document.title
}));
}
});
View.extend = function(subclass) {
subclass.prototype = Object.create(View.prototype, {
constructor: {
value: subclass,
enumerable: false,
writable: true,
configurable: true
}
});
// subclass.__proto__ = View;
return subclass;
};
/**
* Route parser from components/serviceworkerware/dist/sww.js
*/
function parseSimplePath(path) {
// Check for named placeholder crowding
if (/\:[a-zA-Z0-9]+\:[a-zA-Z0-9]+/g.test(path)) {
throw new Error('Invalid usage of named placeholders');
}
// Check for mixed placeholder crowdings
var mixedPlaceHolders =
/(\*\:[a-zA-Z0-9]+)|(\:[a-zA-Z0-9]+\:[a-zA-Z0-9]+)|(\:[a-zA-Z0-9]+\*)/g;
if (mixedPlaceHolders.test(path.replace(/\\\*/g,''))) {
throw new Error('Invalid usage of named placeholders');
}
// Try parsing the string and converting special characters into regex
try {
// Parsing anonymous placeholders with simple backslash-escapes
path = path.replace(/(.|^)[*]+/g, function(m,escape) {
return escape==='\\' ? '\\*' : (escape+'(?:.*?)');
});
// Parsing named placeholders with backslash-escapes
var tags = [];
path = path.replace(/(.|^)\:([a-zA-Z0-9]+)/g, function (m, escape, tag) {
if (escape === '\\') { return ':' + tag; }
tags.push(tag);
return escape + '(.+?)';
});
return { regexp: RegExp(path + '$'), tags: tags };
}
// Failed to parse final path as a RegExp
catch (ex) {
throw new Error('Invalid path specified');
}
}
return View;
})();