feat(APIs): custom api instances
This commit is contained in:
parent
e190519040
commit
99debbb7b2
3 changed files with 109 additions and 10 deletions
|
|
@ -2823,8 +2823,32 @@ export function initializeSettings(scrobbler, player, api, ui) {
|
||||||
if (!button) return;
|
if (!button) return;
|
||||||
|
|
||||||
const li = button.closest('li');
|
const li = button.closest('li');
|
||||||
const index = parseInt(li.dataset.index, 10);
|
const type = button.dataset.type || li?.dataset.type || 'api';
|
||||||
const type = li.dataset.type || 'api'; // Default to api if not present
|
|
||||||
|
if (button.classList.contains('add-instance')) {
|
||||||
|
const url = prompt(`Enter custom ${type.toUpperCase()} instance URL (e.g. https://my-instance.com):`);
|
||||||
|
if (url && url.trim()) {
|
||||||
|
let formattedUrl = url.trim();
|
||||||
|
if (!formattedUrl.startsWith('http')) {
|
||||||
|
formattedUrl = 'https://' + formattedUrl;
|
||||||
|
}
|
||||||
|
api.settings.addUserInstance(type, formattedUrl);
|
||||||
|
ui.renderApiSettings();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (button.classList.contains('delete-instance')) {
|
||||||
|
const url = li.dataset.url;
|
||||||
|
if (url && confirm(`Delete custom instance ${url}?`)) {
|
||||||
|
api.settings.removeUserInstance(type, url);
|
||||||
|
ui.renderApiSettings();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = parseInt(li?.dataset.index, 10);
|
||||||
|
if (isNaN(index)) return;
|
||||||
|
|
||||||
const instances = await api.settings.getInstances(type);
|
const instances = await api.settings.getInstances(type);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,25 @@ export const apiSettings = {
|
||||||
'https://tidal-uptime.props-76styles.workers.dev/',
|
'https://tidal-uptime.props-76styles.workers.dev/',
|
||||||
],
|
],
|
||||||
defaultInstances: { api: [], streaming: [] },
|
defaultInstances: { api: [], streaming: [] },
|
||||||
|
userInstances: null,
|
||||||
instancesLoaded: false,
|
instancesLoaded: false,
|
||||||
_loadPromise: null,
|
_loadPromise: null,
|
||||||
|
|
||||||
|
_loadUserInstances() {
|
||||||
|
if (this.userInstances) return this.userInstances;
|
||||||
|
try {
|
||||||
|
const stored = localStorage.getItem('monochrome-user-api-instances-v1');
|
||||||
|
this.userInstances = stored ? JSON.parse(stored) : { api: [], streaming: [] };
|
||||||
|
} catch {
|
||||||
|
this.userInstances = { api: [], streaming: [] };
|
||||||
|
}
|
||||||
|
return this.userInstances;
|
||||||
|
},
|
||||||
|
|
||||||
|
_saveUserInstances() {
|
||||||
|
localStorage.setItem('monochrome-user-api-instances-v1', JSON.stringify(this.userInstances));
|
||||||
|
},
|
||||||
|
|
||||||
async loadInstancesFromGitHub() {
|
async loadInstancesFromGitHub() {
|
||||||
if (this.instancesLoaded) {
|
if (this.instancesLoaded) {
|
||||||
return this.defaultInstances;
|
return this.defaultInstances;
|
||||||
|
|
@ -126,11 +142,42 @@ export const apiSettings = {
|
||||||
let instancesObj;
|
let instancesObj;
|
||||||
|
|
||||||
instancesObj = await this.loadInstancesFromGitHub();
|
instancesObj = await this.loadInstancesFromGitHub();
|
||||||
|
const userInst = this._loadUserInstances();
|
||||||
|
|
||||||
const targetUrls = instancesObj[type] || instancesObj.api || [];
|
const defaultUrls = instancesObj[type] || instancesObj.api || [];
|
||||||
if (targetUrls.length === 0) return [];
|
const userUrls = userInst[type] || [];
|
||||||
|
|
||||||
return targetUrls;
|
const combined = [...userUrls.map((u) => (typeof u === 'string' ? { url: u, isUser: true } : { ...u, isUser: true })), ...defaultUrls];
|
||||||
|
|
||||||
|
if (combined.length === 0) return [];
|
||||||
|
|
||||||
|
return combined;
|
||||||
|
},
|
||||||
|
|
||||||
|
addUserInstance(type, url) {
|
||||||
|
const userInst = this._loadUserInstances();
|
||||||
|
if (!userInst[type]) userInst[type] = [];
|
||||||
|
|
||||||
|
if (!userInst[type].some((u) => (typeof u === 'string' ? u === url : u.url === url))) {
|
||||||
|
userInst[type].push({ url, isUser: true, version: 'custom' });
|
||||||
|
this._saveUserInstances();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
removeUserInstance(type, url) {
|
||||||
|
const userInst = this._loadUserInstances();
|
||||||
|
if (!userInst[type]) return false;
|
||||||
|
|
||||||
|
const initialLength = userInst[type].length;
|
||||||
|
userInst[type] = userInst[type].filter((u) => (typeof u === 'string' ? u !== url : u.url !== url));
|
||||||
|
|
||||||
|
if (userInst[type].length !== initialLength) {
|
||||||
|
this._saveUserInstances();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
async refreshInstances() {
|
async refreshInstances() {
|
||||||
|
|
@ -160,9 +207,23 @@ export const apiSettings = {
|
||||||
saveInstances(instances, type) {
|
saveInstances(instances, type) {
|
||||||
if (type) {
|
if (type) {
|
||||||
try {
|
try {
|
||||||
|
this._loadUserInstances();
|
||||||
|
const userInst = instances.filter((i) => i.isUser);
|
||||||
|
const defaultInst = instances.filter((i) => !i.isUser);
|
||||||
|
|
||||||
|
this.userInstances[type] = userInst;
|
||||||
|
this._saveUserInstances();
|
||||||
|
|
||||||
const stored = localStorage.getItem(this.STORAGE_KEY);
|
const stored = localStorage.getItem(this.STORAGE_KEY);
|
||||||
let fullObj = stored ? JSON.parse(stored) : { api: [], streaming: [] };
|
let fullObj = stored ? JSON.parse(stored) : { api: [], streaming: [] };
|
||||||
fullObj[type] = instances;
|
|
||||||
|
if (fullObj && fullObj.data) {
|
||||||
|
fullObj.data[type] = defaultInst;
|
||||||
|
} else {
|
||||||
|
if (!fullObj) fullObj = { api: [], streaming: [] };
|
||||||
|
fullObj[type] = defaultInst;
|
||||||
|
}
|
||||||
|
|
||||||
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(fullObj));
|
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(fullObj));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to save instances:', e);
|
console.error('Failed to save instances:', e);
|
||||||
|
|
|
||||||
22
js/ui.js
22
js/ui.js
|
|
@ -4797,18 +4797,29 @@ export class UIRenderer {
|
||||||
? instance.name || instance.displayName || instance.id || instanceUrl
|
? instance.name || instance.displayName || instance.id || instanceUrl
|
||||||
: instanceUrl;
|
: instanceUrl;
|
||||||
const instanceVersion = isObject && instance.version ? String(instance.version) : '';
|
const instanceVersion = isObject && instance.version ? String(instance.version) : '';
|
||||||
|
const isUser = isObject && instance.isUser;
|
||||||
const safeName = escapeHtml(instanceName || 'Unknown instance');
|
const safeName = escapeHtml(instanceName || 'Unknown instance');
|
||||||
const safeUrl = escapeHtml(instanceUrl || '');
|
const safeUrl = escapeHtml(instanceUrl || '');
|
||||||
const safeVersion = escapeHtml(instanceVersion);
|
const safeVersion = escapeHtml(instanceVersion);
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<li data-index="${index}" data-type="${type}">
|
<li data-index="${index}" data-type="${type}" data-url="${safeUrl}">
|
||||||
<div style="flex: 1; min-width: 0;">
|
<div style="flex: 1; min-width: 0;">
|
||||||
<div class="instance-url">${safeName}</div>
|
<div class="instance-url">${safeName} ${isUser ? '<span style="font-size: 0.6rem; opacity: 0.7; background: var(--muted); padding: 1px 4px; border-radius: 3px; margin-left: 4px; vertical-align: middle;">U</span>' : ''}</div>
|
||||||
${safeUrl && safeUrl !== safeName ? `<div style="font-size: 0.8rem; color: var(--muted-foreground); margin-top: 0.15rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">${safeUrl}</div>` : ''}
|
${safeUrl && safeUrl !== safeName ? `<div style="font-size: 0.8rem; color: var(--muted-foreground); margin-top: 0.15rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">${safeUrl}</div>` : ''}
|
||||||
${safeVersion ? `<div style="font-size: 0.75rem; color: var(--muted-foreground); margin-top: 0.1rem;">v${safeVersion}</div>` : ''}
|
${safeVersion ? `<div style="font-size: 0.75rem; color: var(--muted-foreground); margin-top: 0.1rem;">v${safeVersion}</div>` : ''}
|
||||||
</div>
|
</div>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
|
${
|
||||||
|
isUser
|
||||||
|
? `
|
||||||
|
<button class="delete-instance" title="Delete Instance">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<path d="M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
|
||||||
|
</svg>
|
||||||
|
</button>`
|
||||||
|
: ''
|
||||||
|
}
|
||||||
<button class="move-up" title="Move Up" ${index === 0 ? 'disabled' : ''}>
|
<button class="move-up" title="Move Up" ${index === 0 ? 'disabled' : ''}>
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
<path d="M12 19V5M5 12l7-7 7 7"/>
|
<path d="M12 19V5M5 12l7-7 7 7"/>
|
||||||
|
|
@ -4826,8 +4837,11 @@ export class UIRenderer {
|
||||||
.join('');
|
.join('');
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<li class="group-header" style="font-weight: bold; padding: 1rem 0 0.5rem; background: transparent; border: none; pointer-events: none;">
|
<li class="group-header" style="display: flex; justify-content: space-between; align-items: center; font-weight: bold; padding: 1rem 0 0.5rem; background: transparent; border: none;">
|
||||||
${type === 'api' ? 'API Instances' : 'Streaming Instances'}
|
<span>${type === 'api' ? 'API Instances' : 'Streaming Instances'}</span>
|
||||||
|
<button class="add-instance" data-type="${type}" title="Add Custom Instance" style="background: var(--primary); color: var(--primary-foreground); border: none; padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.75rem; cursor: pointer; pointer-events: auto;">
|
||||||
|
Add
|
||||||
|
</button>
|
||||||
</li>
|
</li>
|
||||||
${listHtml}
|
${listHtml}
|
||||||
`;
|
`;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue