mirror of
https://github.com/danieladov/jellyfin-plugin-skin-manager.git
synced 2026-03-18 21:30:33 +01:00
Some changes
This commit is contained in:
@@ -50,7 +50,10 @@ class HistoryController {
|
||||
}
|
||||
|
||||
showSkin() {
|
||||
this.optionsElement.innerHTML = this.currentSkin.generateHTML({ includePreview: false });
|
||||
this.optionsElement.innerHTML = this.currentSkin.generateHTML({
|
||||
includePreview: false,
|
||||
includeLivePreview: false
|
||||
});
|
||||
this.currentSkin.attachEventListeners();
|
||||
}
|
||||
|
||||
@@ -61,6 +64,40 @@ class HistoryController {
|
||||
console.log(`Skin changed to: ${this.currentSkin.name}`);
|
||||
}
|
||||
|
||||
async applyCurrentSkin() {
|
||||
if (!this.currentSkin) {
|
||||
return;
|
||||
}
|
||||
|
||||
const css = this.currentSkin.generateCSS();
|
||||
const appliedSkinName = this.currentSkin.name;
|
||||
|
||||
try {
|
||||
const serverConfig = await ApiClient.getServerConfiguration();
|
||||
await ApiClient.updateServerConfiguration(serverConfig);
|
||||
|
||||
const brandingConfig = await ApiClient.getNamedConfiguration("branding");
|
||||
const existingCss = brandingConfig && typeof brandingConfig.CustomCss === "string"
|
||||
? brandingConfig.CustomCss
|
||||
: "";
|
||||
|
||||
if (existingCss && !this.configController.isManagedCss(existingCss)) {
|
||||
await this.configController.saveUserCss(existingCss);
|
||||
}
|
||||
|
||||
brandingConfig.CustomCss = css;
|
||||
await ApiClient.updateNamedConfiguration("branding", brandingConfig);
|
||||
Dashboard.processServerConfigurationUpdateResult();
|
||||
|
||||
await this.configController.saveSkin(this.currentSkin);
|
||||
await this.configController.setSelectedSkin(appliedSkinName);
|
||||
|
||||
window.location.reload(true);
|
||||
} catch (error) {
|
||||
console.error("Error applying skin from history:", error);
|
||||
}
|
||||
}
|
||||
|
||||
initEventListeners() {
|
||||
this.setSkinButton.addEventListener('click', () => {
|
||||
if (this.currentSkin) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,21 +10,23 @@ class Skin {
|
||||
console.log(`Skin "${this.name}" initialized with ${this.categories.length} categories.`);
|
||||
}
|
||||
|
||||
generateHTML({ includePreview = true } = {}) {
|
||||
generateHTML({ includePreview = true, includeLivePreview = true } = {}) {
|
||||
const categoriesHTML = this.categories
|
||||
.map(category => category.generateHTML())
|
||||
.join('');
|
||||
|
||||
const previewsHTML = includePreview ? this.generatePreviewHTML() : '';
|
||||
const hasPreviews = includePreview && Array.isArray(this.previews) && this.previews.length > 0;
|
||||
const previewsHTML = hasPreviews ? this.generatePreviewHTML() : '';
|
||||
const livePreviewHTML = includeLivePreview ? this.generateLivePreviewHTML() : '';
|
||||
|
||||
return `
|
||||
<div data-role="controlgroup" class="optionsContainer">
|
||||
<div data-role="controlgroup" class="optionsContainer" data-has-previews="${hasPreviews}">
|
||||
<div class="categoriesContainer">
|
||||
${categoriesHTML}
|
||||
</div>
|
||||
<br/>
|
||||
${previewsHTML}
|
||||
${hasPreviews ? `<div class="previewsContainer">${previewsHTML}</div>` : ""}
|
||||
</div>
|
||||
${livePreviewHTML}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -53,19 +55,66 @@ class Skin {
|
||||
}
|
||||
|
||||
generatePreviewHTML() {
|
||||
if (!this.previews || this.previews.length === 0) return '';
|
||||
const hasPreviews = Array.isArray(this.previews) && this.previews.length > 0;
|
||||
const slides = hasPreviews
|
||||
? this.previews.map((preview, index) => {
|
||||
const label = preview.name || `Preview ${index + 1}`;
|
||||
return `
|
||||
<div class="previewSlide" data-index="${index}">
|
||||
<div class="previewSlide-imgWrapper">
|
||||
<img src="${preview.url}" alt="${label}" loading="lazy">
|
||||
</div>
|
||||
<div class="previewSlide-label">${label}</div>
|
||||
</div>
|
||||
`;
|
||||
}).join("")
|
||||
: `<p class="previewEmpty">Esta skin no tiene capturas todavia.</p>`;
|
||||
|
||||
const dots = hasPreviews
|
||||
? `<div class="previewDots">
|
||||
${this.previews.map((_, index) => `<button type="button" class="previewDot" data-index="${index}" aria-label="Vista ${index + 1}"></button>`).join("")}
|
||||
</div>`
|
||||
: "";
|
||||
|
||||
return `
|
||||
<div class="verticalSection verticalSection-extrabottompadding previewSection">
|
||||
<h2 class="sectionTitle">Previews</h2>
|
||||
${this.previews
|
||||
.map(p => `
|
||||
<fieldset class="verticalSection verticalSection-extrabottompadding">
|
||||
<img src="${p.url}" alt="${p.name || ''}">
|
||||
<legend>${p.name}</legend>
|
||||
</fieldset>`
|
||||
)
|
||||
.join("")}
|
||||
<div class="verticalSection verticalSection-extrabottompadding previewSection" data-has-previews="${hasPreviews}">
|
||||
<div class="previewSection-header">
|
||||
<div>
|
||||
<h2 class="sectionTitle">Previsualizacion</h2>
|
||||
<p class="sectionSubtitle">Recorre capturas estaticas de la skin.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="previewCarousel" role="region" aria-label="Capturas de la skin">
|
||||
${hasPreviews ? '<button type="button" class="previewNav previewNav-prev" data-action="prev" aria-label="Anterior">←</button>' : ''}
|
||||
<div class="previewViewport">
|
||||
<div class="previewTrack" id="skinPreviewTrack">
|
||||
${slides}
|
||||
</div>
|
||||
</div>
|
||||
${hasPreviews ? '<button type="button" class="previewNav previewNav-next" data-action="next" aria-label="Siguiente">→</button>' : ''}
|
||||
</div>
|
||||
${dots}
|
||||
<div class="previewCaption" id="skinPreviewCaption"></div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
generateLivePreviewHTML() {
|
||||
return `
|
||||
<div class="livePreviewShell" id="livePreviewShell">
|
||||
<div class="livePreviewHeader">
|
||||
<div>
|
||||
<h3 class="livePreviewTitle">Vista previa en vivo</h3>
|
||||
<p class="livePreviewHint">Se recarga automaticamente al cambiar opciones. Siempre abre /web/#/home.</p>
|
||||
</div>
|
||||
<button is="emby-button" type="button" class="previewAction previewAction-secondary" id="livePreviewExpand">
|
||||
<span>Ampliar vista previa</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="livePreviewFrameWrapper">
|
||||
<iframe id="skinLivePreviewFrame" title="Vista previa del skin" sandbox="allow-same-origin allow-scripts" loading="lazy"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -85,4 +134,3 @@ class Skin {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -170,21 +170,35 @@
|
||||
border: none;
|
||||
}
|
||||
|
||||
.optionsContainer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 10px;
|
||||
}
|
||||
.optionsContainer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.optionsContainer .categoriesContainer {
|
||||
flex: 6;
|
||||
}
|
||||
.optionsContainer .categoriesContainer {
|
||||
flex: 6;
|
||||
}
|
||||
|
||||
.previewsContainer {
|
||||
flex: 5;
|
||||
min-width: 320px;
|
||||
}
|
||||
|
||||
.optionsContainer[data-has-previews="false"] .previewsContainer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.optionsContainer[data-has-previews="false"] .categoriesContainer {
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
|
||||
|
||||
.optionsContainer:only-child .categoriesContainer,
|
||||
.optionsContainer .categoriesContainer:only-child {
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
.optionsContainer:only-child .categoriesContainer,
|
||||
.optionsContainer .categoriesContainer:only-child {
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.optionsContainer {
|
||||
@@ -195,7 +209,11 @@
|
||||
.optionsContainer div {
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.optionsContainer .previewSection {
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.fontPickerInput {
|
||||
width: 100%;
|
||||
@@ -215,9 +233,216 @@
|
||||
}
|
||||
|
||||
|
||||
.previewSection {
|
||||
max-width: fit-content;
|
||||
}
|
||||
.previewSection {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
padding: 12px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 14px;
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
}
|
||||
|
||||
.previewSection-header {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.previewActions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.previewAction {
|
||||
min-width: 160px;
|
||||
}
|
||||
|
||||
.previewAction-secondary {
|
||||
background: rgba(255, 255, 255, 0.06) !important;
|
||||
}
|
||||
|
||||
.sectionSubtitle {
|
||||
margin: 4px 0 0;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.previewCarousel {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.previewCarousel:only-child {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.previewViewport {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
min-height: 280px;
|
||||
}
|
||||
|
||||
.previewTrack {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transition: transform 0.35s ease;
|
||||
}
|
||||
|
||||
.previewSection[data-has-previews="false"] .previewViewport {
|
||||
min-height: 120px;
|
||||
}
|
||||
|
||||
.previewSlide {
|
||||
flex: 0 0 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.previewSlide-imgWrapper {
|
||||
flex: 1;
|
||||
background: linear-gradient(135deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.02));
|
||||
}
|
||||
|
||||
.previewSlide img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.previewSlide-label {
|
||||
padding: 10px 12px;
|
||||
font-weight: 600;
|
||||
background: rgba(0, 0, 0, 0.25);
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
.previewCaption {
|
||||
margin: 0;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.previewDots {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.previewDot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.4);
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.previewDot.is-active {
|
||||
background: #00a4dc;
|
||||
border-color: #00a4dc;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.previewNav {
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
color: inherit;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.1rem;
|
||||
transition: background 0.2s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
.previewNav:hover {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.previewEmpty {
|
||||
margin: 0;
|
||||
padding: 16px;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.previewSection[data-has-previews="false"] .previewDots,
|
||||
.previewSection[data-has-previews="false"] .previewNav {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.livePreviewShell {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
margin-top: 6px;
|
||||
padding: 12px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.livePreviewHeader {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.livePreviewTitle {
|
||||
margin: 0 0 4px;
|
||||
}
|
||||
|
||||
.livePreviewHint {
|
||||
margin: 0;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.livePreviewSync {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.livePreviewFrameWrapper {
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: #0a0a0a;
|
||||
min-height: 340px;
|
||||
}
|
||||
|
||||
#livePreviewShell[data-expanded="true"] .livePreviewFrameWrapper {
|
||||
min-height: 70vh;
|
||||
max-height: calc(100vh - 180px);
|
||||
}
|
||||
|
||||
#skinLivePreviewFrame {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.textareaLabel {
|
||||
display: inline-block;
|
||||
|
||||
Reference in New Issue
Block a user