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;
|
||||
|
||||
const li = button.closest('li');
|
||||
const index = parseInt(li.dataset.index, 10);
|
||||
const type = li.dataset.type || 'api'; // Default to api if not present
|
||||
const type = button.dataset.type || li?.dataset.type || 'api';
|
||||
|
||||
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);
|
||||
|
||||
|
|
|
|||
|
|
@ -6,9 +6,25 @@ export const apiSettings = {
|
|||
'https://tidal-uptime.props-76styles.workers.dev/',
|
||||
],
|
||||
defaultInstances: { api: [], streaming: [] },
|
||||
userInstances: null,
|
||||
instancesLoaded: false,
|
||||
_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() {
|
||||
if (this.instancesLoaded) {
|
||||
return this.defaultInstances;
|
||||
|
|
@ -126,11 +142,42 @@ export const apiSettings = {
|
|||
let instancesObj;
|
||||
|
||||
instancesObj = await this.loadInstancesFromGitHub();
|
||||
const userInst = this._loadUserInstances();
|
||||
|
||||
const targetUrls = instancesObj[type] || instancesObj.api || [];
|
||||
if (targetUrls.length === 0) return [];
|
||||
const defaultUrls = instancesObj[type] || instancesObj.api || [];
|
||||
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() {
|
||||
|
|
@ -160,9 +207,23 @@ export const apiSettings = {
|
|||
saveInstances(instances, type) {
|
||||
if (type) {
|
||||
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);
|
||||
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));
|
||||
} catch (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
|
||||
: instanceUrl;
|
||||
const instanceVersion = isObject && instance.version ? String(instance.version) : '';
|
||||
const isUser = isObject && instance.isUser;
|
||||
const safeName = escapeHtml(instanceName || 'Unknown instance');
|
||||
const safeUrl = escapeHtml(instanceUrl || '');
|
||||
const safeVersion = escapeHtml(instanceVersion);
|
||||
|
||||
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 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>` : ''}
|
||||
${safeVersion ? `<div style="font-size: 0.75rem; color: var(--muted-foreground); margin-top: 0.1rem;">v${safeVersion}</div>` : ''}
|
||||
</div>
|
||||
<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' : ''}>
|
||||
<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"/>
|
||||
|
|
@ -4826,8 +4837,11 @@ export class UIRenderer {
|
|||
.join('');
|
||||
|
||||
return `
|
||||
<li class="group-header" style="font-weight: bold; padding: 1rem 0 0.5rem; background: transparent; border: none; pointer-events: none;">
|
||||
${type === 'api' ? 'API Instances' : 'Streaming Instances'}
|
||||
<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;">
|
||||
<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>
|
||||
${listHtml}
|
||||
`;
|
||||
|
|
|
|||
Loading…
Reference in a new issue