refactor: clients section to dynamically fetch from app-directory (#5380)

This commit is contained in:
Eduardo Mozart de Oliveira
2026-07-23 21:56:19 -03:00
committed by GitHub
parent c6bef3397d
commit c58fd6d8c6
3 changed files with 172 additions and 244 deletions
-180
View File
@@ -1,180 +0,0 @@
---
- name: "Android"
type: "official"
github: "https://github.com/moonlight-stream/moonlight-android"
icons:
- src: "https://cdn.jsdelivr.net/npm/simple-icons@16/icons/android.svg"
alt: "Android"
invert: true
downloads:
- label: "Get it on Google Play"
url: "https://play.google.com/store/apps/details?id=com.limelight"
img_src: "https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png"
img_height: 60
- label: "Available at Amazon Appstore"
url: "https://www.amazon.com/gp/product/B00JK4MFN2"
img_src: "https://images-na.ssl-images-amazon.com/images/G/01/mobile-apps/devportal2/res/images/amazon-appstore-badge-english-black.png" # yamllint disable rule:line-length
img_height: 60
img_style: "padding: 10px;"
- label: "Get it on F-Droid"
url: "https://f-droid.org/packages/com.limelight"
img_src: "https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
img_height: 60
- name: "iOS"
type: "official"
github: "https://github.com/moonlight-stream/moonlight-ios"
icons:
- src: "https://cdn.jsdelivr.net/npm/simple-icons@16/icons/ios.svg"
alt: "iOS"
invert: true
- src: "https://cdn.jsdelivr.net/npm/simple-icons@16/icons/appletv.svg"
alt: "Apple TV"
invert: true
downloads:
- label: "Download on the App Store"
url: "https://apps.apple.com/us/app/moonlight-game-streaming/id1000551566"
img_src: "https://developer.apple.com/assets/elements/badges/download-on-the-app-store.svg"
img_height: 40
- label: "Download on Apple TV"
url: "https://apps.apple.com/us/app/moonlight-game-streaming/id1000551566"
img_src: "https://developer.apple.com/app-store/marketing/guidelines/images/badge-download-on-apple-tv.svg"
img_height: 40
- name: "QT"
type: "official"
github: "https://github.com/moonlight-stream/moonlight-qt"
icons:
- src: "https://cdn.jsdelivr.net/npm/simple-icons@16/icons/linux.svg"
alt: "Linux"
invert: true
- src: "https://cdn.jsdelivr.net/npm/simple-icons@16/icons/macos.svg"
alt: "macOS"
invert: true
- src: "https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.1/icons/windows.svg"
alt: "Windows"
invert: true
- src: "https://cdn.jsdelivr.net/npm/simple-icons@16/icons/steam.svg"
alt: "Steam"
invert: true
downloads:
- label: "Download on GitHub"
url: "https://github.com/moonlight-stream/moonlight-qt/releases"
icon_fa: "fab fa-github"
btn_class: "btn btn-info"
- name: "Embedded"
type: "official"
github: "https://github.com/moonlight-stream/moonlight-embedded"
icons:
- src: "https://cdn.jsdelivr.net/npm/simple-icons@16/icons/raspberrypi.svg"
alt: "Raspberry Pi"
invert: true
downloads:
- label: "Download"
url: "https://github.com/irtimmer/moonlight-embedded/wiki/Packages"
icon_fa: "fas fa-download"
btn_class: "btn btn-info"
- name: "Xbox One/Series"
type: "community"
github: "https://github.com/TheElixZammuto/moonlight-xbox"
icons:
- src: "https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.1/icons/xbox.svg"
alt: "Xbox"
invert: true
downloads:
- label: "Get it from Microsoft"
url: "https://apps.microsoft.com/store/detail/moonlight-uwp/9MW1BS08ZBTH"
img_src: "https://get.microsoft.com/images/en-us%20dark.svg"
img_height: 40
- name: "PS Vita"
type: "community"
github: "https://github.com/xyzz/vita-moonlight"
icons:
- src: "https://cdn.jsdelivr.net/npm/simple-icons@16/icons/playstationvita.svg"
alt: "PlayStation Vita"
invert: true
downloads:
- label: "Download on GitHub"
url: "https://github.com/xyzz/vita-moonlight/releases"
icon_fa: "fab fa-github"
btn_class: "btn btn-info"
- name: "Moonlight Switch"
type: "community"
github: "https://github.com/XITRIX/Moonlight-Switch"
icons:
- src: "https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.1/icons/nintendo-switch.svg"
alt: "Nintendo Switch"
invert: true
- src: "https://cdn.jsdelivr.net/npm/simple-icons@16/icons/android.svg"
alt: "Android"
invert: true
- src: "https://cdn.jsdelivr.net/npm/simple-icons@16/icons/appletv.svg"
alt: "Apple TV"
invert: true
- src: "https://cdn.jsdelivr.net/npm/simple-icons@16/icons/ios.svg"
alt: "iOS"
invert: true
- src: "https://cdn.jsdelivr.net/npm/simple-icons@16/icons/macos.svg"
alt: "macOS"
invert: true
downloads:
- label: "Download on GitHub"
url: "https://github.com/XITRIX/Moonlight-Switch/releases"
icon_fa: "fab fa-github"
btn_class: "btn btn-info"
- name: "Nintendo Wii U"
type: "community"
github: "https://github.com/GaryOderNichts/moonlight-wiiu"
icons:
- src: "https://cdn.jsdelivr.net/npm/simple-icons@13/icons/wiiu.svg"
alt: "Wii U"
invert: true
downloads:
- label: "Download"
url: "https://github.com/GaryOderNichts/moonlight-wiiu#quick-start"
icon_fa: "fas fa-download"
btn_class: "btn btn-info"
- name: "New Nintendo 3DS"
type: "community"
github: "https://github.com/zoeyjodon/moonlight-N3DS"
icons:
- src: "https://cdn.jsdelivr.net/npm/simple-icons@13/icons/nintendo3ds.svg"
alt: "3DS"
invert: true
downloads:
- label: "Download"
url: "https://github.com/zoeyjodon/moonlight-N3DS"
icon_fa: "fas fa-download"
btn_class: "btn btn-info"
- name: "LG webOS TV"
type: "community"
github: "https://github.com/mariotaku/moonlight-tv"
icons:
- src: "https://cdn.jsdelivr.net/npm/simple-icons@16/icons/lg.svg"
alt: "LG webOS TV"
invert: true
downloads:
- label: "Download"
url: "https://github.com/mariotaku/moonlight-tv#download"
icon_fa: "fas fa-download"
btn_class: "btn btn-info"
- name: "Samsung Tizen TV"
type: "community"
github: "https://github.com/brightcraft/moonlight-tizen"
icons:
- src: "https://cdn.jsdelivr.net/npm/simple-icons@16/icons/samsung.svg"
alt: "Samsung Tizen TV"
invert: true
downloads:
- label: "Download on GitHub"
url: "https://github.com/brightcraft/moonlight-tizen/releases"
icon_fa: "fab fa-github"
btn_class: "btn btn-info"
+109 -51
View File
@@ -59,57 +59,8 @@ cover-img:
<section class="py-5" id="Clients"> <section class="py-5" id="Clients">
<div class="container px-auto"> <div class="container px-auto">
<h2 class="text-center fw-bolder my-5">Clients</h2> <h2 class="text-center fw-bolder my-5">Clients</h2>
<div class="row gx-5"> <div class="row gx-5" id="clients-container">
{% for client in site.data.clients %} <!-- Dynamically populated from sunshine.json -->
<div class="col-md-6 col-lg-4 mb-5">
<div class="card-custom h-100 shadow border-0 rounded-0 d-flex flex-column">
<div class="card-body p-4">
<div class="d-flex align-items-center">
<div class="icon">
{% for icon in client.icons %}
<img {% if icon.invert %}class="invert"{% endif %} src="{{ icon.src }}" alt="{{ icon.alt }}"/>
{% endfor %}
</div>
<div class="ms-3">
<h5 class="fw-bolder mb-0">
<a href="{{ client.github }}" target="_blank" class="text-decoration-none project-card-link crowdin-ignore">
{{ client.name }}
</a>
</h5>
</div>
<div class="ms-auto">
{% if client.type == "official" %}
<span class="badge text-bg-info rounded-pill">Official</span>
{% elsif client.type == "community" %}
<span class="badge text-bg-warning rounded-pill">Community</span>
{% endif %}
</div>
</div>
</div>
{% if client.downloads %}
<div class="card-footer p-3 px-4">
{% for download in client.downloads %}
<div class="{% unless forloop.last %}pb-3{% endunless %}">
{% if download.img_src %}
<a href="{{ download.url }}" target="_blank"{% if download.btn_class %} class="{{ download.btn_class }}"{% endif %}>
<img alt="{{ download.label }}"
src="{{ download.img_src }}"
height="{{ download.img_height }}"
{% if download.img_style %}style="{{ download.img_style }}"{% endif %}/>
</a>
{% else %}
<a href="{{ download.url }}" target="_blank" class="{{ download.btn_class | default: 'btn btn-info' }}">
{% if download.icon_fa %}<i class="{{ download.icon_fa }}"></i>{% endif %}
{{ download.label }}
</a>
{% endif %}
</div>
{% endfor %}
</div>
{% endif %}
</div>
</div>
{% endfor %}
</div> </div>
</div> </div>
</section> </section>
@@ -245,4 +196,111 @@ cover-img:
} }
} }
}); });
// Fetch clients from app directory
fetch('https://app.lizardbyte.dev/app-directory/sunshine.json')
.then(response => response.json())
.then(data => {
const clientsContainer = document.getElementById('clients-container');
if (!clientsContainer) return;
const apps = data.apps || [];
apps.forEach(app => {
let iconsHtml = '';
// Dynamic generation from JSON icons array
if (app.icons && Array.isArray(app.icons)) {
app.icons.forEach(icon => {
iconsHtml += `<img ${icon.invert ? 'class="invert"' : ''} src="${icon.src}" alt="${icon.alt}"/>`;
});
}
const typeBadge = app.official
? '<span class="badge text-bg-info rounded-pill">Official</span>'
: '<span class="badge text-bg-warning rounded-pill">Community</span>';
let downloadsHtml = '';
// Dynamic generation from JSON downloads array
if (app.downloads && Array.isArray(app.downloads)) {
app.downloads.forEach((download, index) => {
const isLast = index === app.downloads.length - 1;
const pbClass = isLast ? '' : ' pb-3';
if (download.img) {
let styleAttr = '';
let heightAttr = '40';
let imgHost = '';
try { imgHost = new URL(download.img).hostname; } catch (e) {}
if (imgHost === 'play.google.com' || imgHost.endsWith('.ssl-images-amazon.com') || imgHost === 'fdroid.gitlab.io') {
heightAttr = '60';
}
if (imgHost.endsWith('.ssl-images-amazon.com')) {
styleAttr = 'style="padding: 10px;"';
}
downloadsHtml += `
<div class="${pbClass.trim()}">
<a href="${download.url}" target="_blank">
<img alt="${download.label}"
src="${download.img}"
height="${heightAttr}"
${styleAttr}/>
</a>
</div>
`;
} else {
let btnClass = 'btn btn-info';
let iconHtml = '<i class="fas fa-download"></i> ';
let urlHost = '';
try { urlHost = new URL(download.url).hostname; } catch (e) {}
if (urlHost === 'github.com') {
iconHtml = '<i class="fab fa-github"></i> ';
}
downloadsHtml += `
<div class="${pbClass.trim()}">
<a href="${download.url}" target="_blank" class="${btnClass}">
${iconHtml}${download.label}
</a>
</div>
`;
}
});
}
const projectLink = (app.links && app.links.github) || (app.links && app.links.website) || '#';
const clientHtml = `
<div class="col-md-6 col-lg-4 mb-5">
<div class="card-custom h-100 shadow border-0 rounded-0 d-flex flex-column">
<div class="card-body p-4">
<div class="d-flex align-items-center">
<div class="icon">
${iconsHtml}
</div>
<div class="ms-3">
<h5 class="fw-bolder mb-0">
<a href="${projectLink}" target="_blank" class="text-decoration-none project-card-link crowdin-ignore">
${app.name}
</a>
</h5>
</div>
<div class="ms-auto">
${typeBadge}
</div>
</div>
</div>
${downloadsHtml ? `
<div class="card-footer p-3 px-4">
${downloadsHtml}
</div>` : ''}
</div>
</div>
`;
clientsContainer.innerHTML += clientHtml;
});
});
</script> </script>
+55 -5
View File
@@ -133,22 +133,46 @@
</div> </div>
<!-- Action Buttons --> <!-- Action Buttons -->
<div class="d-flex gap-2 flex-wrap"> <div class="d-flex gap-2 flex-wrap align-items-center">
<template v-if="app.downloads && app.downloads.length > 0">
<a
v-for="(download, index) in app.downloads"
:key="index"
:href="download.url"
target="_blank"
rel="noopener noreferrer"
:class="download.img ? 'flex-fill text-center d-inline-flex align-items-center justify-content-center' : 'btn btn-primary btn-sm flex-fill d-inline-flex align-items-center justify-content-center gap-1'">
<img v-if="download.img"
:src="download.img"
:alt="download.label"
:height="getDownloadHeight(download.img)"
:style="getDownloadStyle(download.img)" />
<template v-else>
<simple-icon v-if="isGithubUrl(download.url)" icon="GitHub" :size="16" class="icon"></simple-icon>
<arrow-down-circle v-else :size="16" class="icon"></arrow-down-circle>
{{ download.label }}
</template>
</a>
</template>
<template v-else>
<a <a
v-if="app.links && app.links.download" v-if="app.links && app.links.download"
:href="app.links.download" :href="app.links.download"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
class="btn btn-primary btn-sm flex-fill"> class="btn btn-primary btn-sm flex-fill d-inline-flex align-items-center justify-content-center gap-1">
<arrow-down-circle :size="16" class="icon"></arrow-down-circle> <arrow-down-circle :size="16" class="icon"></arrow-down-circle>
{{ $t('featured.get') }} {{ $t('featured.get') }}
</a> </a>
</template>
<a <a
v-if="app.links && app.links.documentation" v-if="app.links && app.links.documentation"
:href="app.links.documentation" :href="app.links.documentation"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
class="btn btn-outline-primary btn-sm" class="btn btn-outline-primary btn-sm d-inline-flex align-items-center justify-content-center gap-1"
:title="$t('featured.documentation')"> :title="$t('featured.documentation')">
<external-link :size="16" class="icon"></external-link> <external-link :size="16" class="icon"></external-link>
{{ $t('featured.docs') }} {{ $t('featured.docs') }}
@@ -158,7 +182,7 @@
:href="app.links.website" :href="app.links.website"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
class="btn btn-outline-secondary btn-sm" class="btn btn-outline-secondary btn-sm d-inline-flex align-items-center justify-content-center"
:title="$t('featured.website')"> :title="$t('featured.website')">
<external-link :size="16" class="icon"></external-link> <external-link :size="16" class="icon"></external-link>
</a> </a>
@@ -167,7 +191,7 @@
:href="app.links.github" :href="app.links.github"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
class="btn btn-outline-secondary btn-sm" class="btn btn-outline-secondary btn-sm d-inline-flex align-items-center justify-content-center"
:title="$t('featured.github')"> :title="$t('featured.github')">
<simple-icon icon="GitHub" :size="16" class="icon"></simple-icon> <simple-icon icon="GitHub" :size="16" class="icon"></simple-icon>
</a> </a>
@@ -438,6 +462,32 @@
const relative = formatDistanceToNow(date, { addSuffix: true }); const relative = formatDistanceToNow(date, { addSuffix: true });
return { relative, absolute }; return { relative, absolute };
},
getDownloadHeight(img) {
if (!img) return '40';
let imgHost = '';
try { imgHost = new URL(img).hostname; } catch (e) {}
if (imgHost === 'play.google.com' || imgHost.endsWith('.ssl-images-amazon.com') || imgHost === 'fdroid.gitlab.io') {
return '60';
}
return '40';
},
getDownloadStyle(img) {
if (!img) return '';
let imgHost = '';
try { imgHost = new URL(img).hostname; } catch (e) {}
if (imgHost.endsWith('.ssl-images-amazon.com')) {
return 'padding: 10px;';
}
return '';
},
isGithubUrl(url) {
if (!url) return false;
try {
return new URL(url).hostname === 'github.com';
} catch (e) {
return false;
}
} }
}, },
}); });