mirror of
https://github.com/ranaldsgift/KefinTweaks.git
synced 2026-11-05 15:52:47 +01:00
Support hiding items in Custom Item sections, and allow adding items to default Kefin custom item sections
This commit is contained in:
1 parent
9c2838f470
commit
07aff27058
9 files changed
+338
-43
No files matched your search
@@ -1008,20 +1008,7 @@
|
||||
}
|
||||
|
||||
const browseSection = studiosGroup.sections.find((s) => s?.id === BROWSE_BY_STUDIO_SECTION_ID);
|
||||
const catalogNames = new Set(BROWSE_BY_STUDIO_CATALOG.map((entry) => entry.name));
|
||||
|
||||
// Prune items no longer in Kefin catalog (even when catalog is otherwise fully resolved)
|
||||
if (browseSection && Array.isArray(browseSection.items)) {
|
||||
const beforeLen = browseSection.items.length;
|
||||
browseSection.items = browseSection.items.filter(
|
||||
(item) => item?.Name && catalogNames.has(item.Name)
|
||||
);
|
||||
if (browseSection.items.length !== beforeLen) {
|
||||
hasChanges = true;
|
||||
LOG(`Pruned ${beforeLen - browseSection.items.length} browse-by-studio studios not in catalog`);
|
||||
}
|
||||
}
|
||||
|
||||
// Append-only: do not prune user-kept studios that leave the Kefin catalog
|
||||
const existingItems = Array.isArray(browseSection?.items) ? browseSection.items : [];
|
||||
|
||||
if (browseSection && isBrowseByStudioCatalogFullyResolved(existingItems)) {
|
||||
|
||||
@@ -563,8 +563,9 @@
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1em;
|
||||
justify-content: center;
|
||||
justify-content: start;
|
||||
padding: 0.5em 0 1em;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:is([data-modal-id="kefin-homescreen-section-editor"], [data-modal-id="kefin-homescreen-section-create-flow"]) .hsse-custom-item-tile,
|
||||
@@ -576,6 +577,68 @@
|
||||
gap: 0.35em;
|
||||
}
|
||||
|
||||
.hsse-custom-items-grid > * {
|
||||
max-width: 120px;
|
||||
}
|
||||
.listItemBodyText.hsse-custom-item-tile-label {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
}
|
||||
.hsse-custom-item-edit-badge {
|
||||
position: absolute;
|
||||
bottom: 0.2em;
|
||||
right: 0.2em;
|
||||
}
|
||||
.hsse-custom-item-actions > * {
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
:is([data-modal-id="kefin-homescreen-section-editor"], [data-modal-id="kefin-homescreen-section-create-flow"])
|
||||
.hsse-custom-item-tile.is-hidden
|
||||
.hsse-custom-item-tile-image,
|
||||
:is([data-modal-id="kefin-homescreen-section-editor"], [data-modal-id="kefin-homescreen-section-create-flow"])
|
||||
.hsse-custom-item-tile.is-hidden
|
||||
.hsse-custom-item-tile-label {
|
||||
opacity: 0.4;
|
||||
filter: grayscale(0.35);
|
||||
}
|
||||
|
||||
:is([data-modal-id="kefin-homescreen-section-editor"], [data-modal-id="kefin-homescreen-section-create-flow"])
|
||||
.hsse-custom-item-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.2em;
|
||||
}
|
||||
|
||||
:is([data-modal-id="kefin-homescreen-section-editor"], [data-modal-id="kefin-homescreen-section-create-flow"])
|
||||
.hsse-custom-item-action,
|
||||
:is([data-modal-id="kefin-homescreen-section-editor"], [data-modal-id="kefin-homescreen-section-create-flow"])
|
||||
.hsse-custom-item-trash {
|
||||
font-size: 0;
|
||||
padding: 0.3em;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
:is([data-modal-id="kefin-homescreen-section-editor"], [data-modal-id="kefin-homescreen-section-create-flow"])
|
||||
.hsse-custom-item-action:disabled {
|
||||
opacity: 0.35;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
:is([data-modal-id="kefin-homescreen-section-editor"], [data-modal-id="kefin-homescreen-section-create-flow"])
|
||||
.hsse-custom-item-action-icon,
|
||||
:is([data-modal-id="kefin-homescreen-section-editor"], [data-modal-id="kefin-homescreen-section-create-flow"])
|
||||
.hsse-custom-item-trash-icon {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
:is([data-modal-id="kefin-homescreen-section-editor"], [data-modal-id="kefin-homescreen-section-create-flow"]) .hsse-custom-item-tile-image,
|
||||
:is([data-modal-id="kefin-homescreen-section-editor"], [data-modal-id="kefin-homescreen-section-create-flow"]) .hsse-custom-item-add-tile {
|
||||
width: auto;
|
||||
@@ -1598,7 +1661,7 @@
|
||||
display: grid !important;
|
||||
}
|
||||
|
||||
/* Item source — show only the panel matching the active source (profile rules still gate custom items on non-full profiles) */
|
||||
/* Item source — show only the panel matching the active source */
|
||||
.hsae-root[data-item-source="jellyfin"] .hsae-custom-items-group,
|
||||
.hsae-root[data-item-source="jellyfin"] .hsae-external-list-group {
|
||||
display: none !important;
|
||||
@@ -1660,6 +1723,7 @@
|
||||
}
|
||||
|
||||
.hsse-image-preview img {
|
||||
height: 200px;
|
||||
height: 120px;
|
||||
max-width: 120px;
|
||||
width: auto !important;
|
||||
}
|
||||
@@ -656,6 +656,23 @@
|
||||
return window.KefinHomeScreenCustomItemsEditor;
|
||||
}
|
||||
|
||||
/** User-created / custom-group sections may delete items; default Kefin sections may only hide. */
|
||||
function shouldAllowCustomItemRemove(state) {
|
||||
const id = state?.section?.id || '';
|
||||
if (state?.context?.isCustomSection === true) return true;
|
||||
if (state?.section?.isCustom === true) return true;
|
||||
if (id.startsWith('custom-')) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function getAdvancedCustomItemsOptions(state) {
|
||||
const customEditor = getCustomItemsEditor();
|
||||
if (!customEditor?.getCustomItemsOptions) return {};
|
||||
return customEditor.getCustomItemsOptions({
|
||||
allowRemove: shouldAllowCustomItemRemove(state)
|
||||
});
|
||||
}
|
||||
|
||||
function getCachePresets() {
|
||||
const cache = window.KefinHomeConfig2?.CACHE || window.KefinHomeConfig2?.CACHE_CONFIG || {};
|
||||
return [
|
||||
@@ -1110,7 +1127,7 @@
|
||||
|
||||
const customEditor = getCustomItemsEditor();
|
||||
if (customEditor && state.itemSource === 'custom') {
|
||||
customEditor.collectCustomItemsFromForm(root, state, customEditor.getCustomItemsOptions());
|
||||
customEditor.collectCustomItemsFromForm(root, state, getAdvancedCustomItemsOptions(state));
|
||||
}
|
||||
|
||||
if (state.itemSource === 'external') {
|
||||
@@ -1863,15 +1880,19 @@
|
||||
|
||||
function buildCustomItemsHTML(state) {
|
||||
const customEditor = getCustomItemsEditor();
|
||||
const allowRemove = shouldAllowCustomItemRemove(state);
|
||||
const html = customEditor
|
||||
? customEditor.renderCustomItemsHTML(state, customEditor.getCustomItemsOptions())
|
||||
? customEditor.renderCustomItemsHTML(state, getAdvancedCustomItemsOptions(state))
|
||||
: '<div class="listItemBodyText secondary">Custom items editor not available.</div>';
|
||||
const groupDesc = allowRemove
|
||||
? 'Create cards with custom images and links'
|
||||
: 'Reorder or hide cards. Default section items cannot be deleted.';
|
||||
|
||||
return `
|
||||
<section class="hsae-group hsae-custom-items-group" data-hsae-profiles="full">
|
||||
<section class="hsae-group hsae-custom-items-group" data-hsae-profiles="full minimal postProcessing normal dateCutoffs">
|
||||
<div class="hsae-group-header">
|
||||
<div class="listItemBodyText hsae-group-title">Custom Items</div>
|
||||
<div class="listItemBodyText secondary hsae-group-desc">Create cards with custom images and links</div>
|
||||
<div class="listItemBodyText secondary hsae-group-desc">${escapeHtml(groupDesc)}</div>
|
||||
</div>
|
||||
<div class="hsae-custom-items-stage">${html}</div>
|
||||
</section>
|
||||
@@ -2636,7 +2657,7 @@
|
||||
const liveState = getLiveEditorState(dialog);
|
||||
const customItemsGroup = dialog.querySelector('.hsae-custom-items-group');
|
||||
if (isProfileFieldActive(root, customItemsGroup) && customEditor && liveState?.customItems) {
|
||||
customEditor.collectCustomItemsFromForm(dialog, liveState, customEditor.getCustomItemsOptions());
|
||||
customEditor.collectCustomItemsFromForm(dialog, liveState, getAdvancedCustomItemsOptions(liveState));
|
||||
section.items = liveState.customItems.map(customEditor.buildStaticItemFromDraft).filter(Boolean);
|
||||
} else if (!Array.isArray(section.items)) {
|
||||
section.items = [];
|
||||
@@ -3373,7 +3394,7 @@
|
||||
if (action?.startsWith('hsae-')) {
|
||||
const customEditor = getCustomItemsEditor();
|
||||
if (customEditor && state.itemSource === 'custom') {
|
||||
customEditor.handleAction(action, actionEl, root, state, customEditor.getCustomItemsOptions(), () => refreshEditorBody(modalInstance));
|
||||
customEditor.handleAction(action, actionEl, root, state, getAdvancedCustomItemsOptions(state), () => refreshEditorBody(modalInstance));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -37,9 +37,14 @@
|
||||
addItem: 'wizard-add-custom-item',
|
||||
removeItem: 'wizard-remove-custom-item',
|
||||
toggleImages: 'wizard-toggle-custom-images',
|
||||
toggleHide: 'wizard-toggle-custom-item-hide',
|
||||
moveLeft: 'wizard-move-custom-item-left',
|
||||
moveRight: 'wizard-move-custom-item-right',
|
||||
...(options.actions || {})
|
||||
},
|
||||
intro: options.intro !== false
|
||||
intro: options.intro !== false,
|
||||
// Default Kefin sections pass false — hide only, never delete
|
||||
allowRemove: options.allowRemove !== false
|
||||
};
|
||||
}
|
||||
|
||||
@@ -61,7 +66,8 @@
|
||||
backdropUrl: '',
|
||||
squareUrl: '',
|
||||
bannerUrl: '',
|
||||
logoUrl: ''
|
||||
logoUrl: '',
|
||||
hidden: false
|
||||
};
|
||||
}
|
||||
|
||||
@@ -77,6 +83,9 @@
|
||||
draft.squareUrl = item.squareUrl || '';
|
||||
draft.bannerUrl = item.bannerUrl || '';
|
||||
draft.logoUrl = item.logoUrl || '';
|
||||
draft.hidden = item.hidden === true;
|
||||
if (item.Id || item.id) draft.id = item.Id || item.id;
|
||||
if (item.Type) draft.type = item.Type;
|
||||
return draft;
|
||||
}
|
||||
|
||||
@@ -114,6 +123,33 @@
|
||||
state.customItemAdditionalExpanded = next;
|
||||
}
|
||||
|
||||
function swapCustomItemExpanded(state, fromIndex, toIndex) {
|
||||
if (!state.customItemAdditionalExpanded) return;
|
||||
const a = state.customItemAdditionalExpanded[fromIndex];
|
||||
const b = state.customItemAdditionalExpanded[toIndex];
|
||||
if (a === undefined && b === undefined) return;
|
||||
if (b === undefined) delete state.customItemAdditionalExpanded[fromIndex];
|
||||
else state.customItemAdditionalExpanded[fromIndex] = b;
|
||||
if (a === undefined) delete state.customItemAdditionalExpanded[toIndex];
|
||||
else state.customItemAdditionalExpanded[toIndex] = a;
|
||||
}
|
||||
|
||||
function moveCustomItemExpanded(state, fromIndex, toIndex) {
|
||||
if (!state.customItemAdditionalExpanded || fromIndex === toIndex) return;
|
||||
const next = {};
|
||||
const moved = state.customItemAdditionalExpanded[fromIndex];
|
||||
Object.entries(state.customItemAdditionalExpanded).forEach(([key, value]) => {
|
||||
const i = parseInt(key, 10);
|
||||
if (Number.isNaN(i) || i === fromIndex) return;
|
||||
let dest = i;
|
||||
if (fromIndex < toIndex && i > fromIndex && i <= toIndex) dest = i - 1;
|
||||
else if (fromIndex > toIndex && i >= toIndex && i < fromIndex) dest = i + 1;
|
||||
next[dest] = value;
|
||||
});
|
||||
if (moved !== undefined) next[toIndex] = moved;
|
||||
state.customItemAdditionalExpanded = next;
|
||||
}
|
||||
|
||||
function hasValidCustomItems(items) {
|
||||
return (items || []).some(item => item.name?.trim() && item.posterUrl?.trim());
|
||||
}
|
||||
@@ -138,11 +174,13 @@
|
||||
if (value) provided[key] = value;
|
||||
});
|
||||
|
||||
const item = { Name: name, Type: 'Folder' };
|
||||
const item = { Name: name, Type: draft.type || 'Folder' };
|
||||
if (draft.id) item.Id = draft.id;
|
||||
const cardUrl = draft.cardUrl?.trim();
|
||||
if (cardUrl) item.cardUrl = cardUrl;
|
||||
const cardFooter = draft.cardFooter?.trim();
|
||||
if (cardFooter) item.cardFooter = cardFooter;
|
||||
if (draft.hidden === true) item.hidden = true;
|
||||
|
||||
const keys = Object.keys(provided);
|
||||
if (keys.length === 1 && provided.posterUrl) {
|
||||
@@ -177,9 +215,20 @@
|
||||
}
|
||||
}
|
||||
|
||||
function resolvePreviewUrl(url) {
|
||||
const kefinTweaksRoot = window.KefinTweaksConfig?.kefinTweaksRoot || '';
|
||||
const serverId = (typeof ApiClient !== 'undefined' && typeof ApiClient.serverId === 'function')
|
||||
? ApiClient.serverId()
|
||||
: '';
|
||||
return String(url || '')
|
||||
.replace(/\$\{kefinTweaksRoot\}/g, kefinTweaksRoot)
|
||||
.replace(/\$\{serverId\}/g, serverId)
|
||||
.trim();
|
||||
}
|
||||
|
||||
function updateCustomImagePreview(imgEl, url) {
|
||||
if (!imgEl) return;
|
||||
const trimmed = (url || '').trim();
|
||||
const trimmed = resolvePreviewUrl(url);
|
||||
if (!trimmed) {
|
||||
imgEl.removeAttribute('src');
|
||||
imgEl.style.display = 'none';
|
||||
@@ -268,19 +317,41 @@
|
||||
`;
|
||||
}
|
||||
|
||||
function renderCustomItemTileHTML(index, item, canRemove, options) {
|
||||
function renderCustomItemTileHTML(index, item, canRemove, options, itemCount) {
|
||||
const opts = getOptions(options);
|
||||
const label = item.name?.trim() || `Item ${index + 1}`;
|
||||
const isHidden = item.hidden === true;
|
||||
const canMoveLeft = index > 0;
|
||||
const canMoveRight = index < itemCount - 1;
|
||||
const hideIcon = isHidden ? 'visibility_off' : 'visibility';
|
||||
const hideLabel = isHidden ? `Show ${label}` : `Hide ${label}`;
|
||||
const removeTitle = canRemove
|
||||
? `Remove ${label}`
|
||||
: (opts.allowRemove === false
|
||||
? 'Default section items cannot be deleted — hide instead'
|
||||
: `Remove ${label}`);
|
||||
|
||||
return `
|
||||
<div class="hsse-custom-item-tile" data-custom-item-index="${index}">
|
||||
<div class="hsse-custom-item-tile${isHidden ? ' is-hidden' : ''}" data-custom-item-index="${index}">
|
||||
<button type="button" class="hsse-custom-item-tile-image hsse-image-preview hsse-item-tile-preview" ${actionAttr(opts, 'openEditor')} data-item-index="${index}" aria-label="Edit ${escapeHtml(label)}">
|
||||
<img class="hsse-image-preview-img" alt="" data-tile-index="${index}">
|
||||
<span class="hsse-custom-item-edit-badge">${renderMaterialIcon('edit')}</span>
|
||||
</button>
|
||||
<div class="listItemBodyText hsse-custom-item-tile-label">${escapeHtml(label)}</div>
|
||||
<button type="button" class="emby-button raised hsse-custom-item-trash" ${actionAttr(opts, 'removeItem')} data-item-index="${index}"${canRemove ? '' : ' disabled'} aria-label="Remove ${escapeHtml(label)}">
|
||||
${renderMaterialIcon('delete', 'hsse-custom-item-trash-icon')}
|
||||
</button>
|
||||
<div class="hsse-custom-item-actions">
|
||||
<button type="button" class="emby-button raised hsse-custom-item-action" ${actionAttr(opts, 'moveLeft')} data-item-index="${index}"${canMoveLeft ? '' : ' disabled'} aria-label="Move ${escapeHtml(label)} left">
|
||||
${renderMaterialIcon('chevron_left', 'hsse-custom-item-action-icon')}
|
||||
</button>
|
||||
<button type="button" class="emby-button raised hsse-custom-item-action hsse-custom-item-hide" ${actionAttr(opts, 'toggleHide')} data-item-index="${index}" aria-label="${escapeHtml(hideLabel)}" aria-pressed="${isHidden ? 'true' : 'false'}">
|
||||
${renderMaterialIcon(hideIcon, 'hsse-custom-item-action-icon')}
|
||||
</button>
|
||||
<button type="button" class="emby-button raised hsse-custom-item-action hsse-custom-item-trash" ${actionAttr(opts, 'removeItem')} data-item-index="${index}"${canRemove ? '' : ' disabled'} title="${escapeHtml(removeTitle)}" aria-label="${escapeHtml(removeTitle)}">
|
||||
${renderMaterialIcon('delete', 'hsse-custom-item-action-icon hsse-custom-item-trash-icon')}
|
||||
</button>
|
||||
<button type="button" class="emby-button raised hsse-custom-item-action" ${actionAttr(opts, 'moveRight')} data-item-index="${index}"${canMoveRight ? '' : ' disabled'} aria-label="Move ${escapeHtml(label)} right">
|
||||
${renderMaterialIcon('chevron_right', 'hsse-custom-item-action-icon')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -319,7 +390,7 @@
|
||||
const opts = getOptions(options);
|
||||
ensureCustomItemsInitialized(state);
|
||||
const items = state.customItems;
|
||||
const canRemove = items.length > 1;
|
||||
const canRemove = opts.allowRemove !== false && items.length > 1;
|
||||
|
||||
return `
|
||||
<div class="hsse-custom-items-stage">
|
||||
@@ -329,7 +400,7 @@
|
||||
<p class="listItemBodyText secondary hsse-field-hint">Name and poster required. Link and other images optional.</p>
|
||||
</div>` : ''}
|
||||
<div class="hsse-custom-items-grid">
|
||||
${items.map((item, index) => renderCustomItemTileHTML(index, item, canRemove, opts)).join('')}
|
||||
${items.map((item, index) => renderCustomItemTileHTML(index, item, canRemove, opts, items.length)).join('')}
|
||||
<button type="button" class="hsse-custom-item-add-tile" ${actionAttr(opts, 'addItem')} aria-label="Add item">
|
||||
${renderMaterialIcon('add', 'hsse-custom-item-add-icon')}
|
||||
</button>
|
||||
@@ -339,6 +410,48 @@
|
||||
`;
|
||||
}
|
||||
|
||||
function reindexTileDom(tile, index, itemCount) {
|
||||
if (!tile) return;
|
||||
tile.dataset.customItemIndex = String(index);
|
||||
tile.querySelectorAll('[data-item-index]').forEach((el) => {
|
||||
el.dataset.itemIndex = String(index);
|
||||
});
|
||||
const tileImg = tile.querySelector('img[data-tile-index]');
|
||||
if (tileImg) tileImg.dataset.tileIndex = String(index);
|
||||
|
||||
// Action order: left | hide | delete | right
|
||||
const actions = tile.querySelectorAll('.hsse-custom-item-actions .hsse-custom-item-action');
|
||||
if (actions.length >= 4) {
|
||||
actions[0].disabled = index <= 0;
|
||||
actions[3].disabled = index >= itemCount - 1;
|
||||
}
|
||||
}
|
||||
|
||||
function swapAdjacentCustomItemTiles(root, fromIndex, toIndex) {
|
||||
if (!root || fromIndex === toIndex) return false;
|
||||
const grid = root.querySelector('.hsse-custom-items-grid');
|
||||
if (!grid) return false;
|
||||
const a = grid.querySelector(`[data-custom-item-index="${fromIndex}"]`);
|
||||
const b = grid.querySelector(`[data-custom-item-index="${toIndex}"]`);
|
||||
if (!a || !b) return false;
|
||||
|
||||
const aNext = a.nextSibling;
|
||||
const bNext = b.nextSibling;
|
||||
if (aNext === b) {
|
||||
grid.insertBefore(b, a);
|
||||
} else if (bNext === a) {
|
||||
grid.insertBefore(a, b);
|
||||
} else {
|
||||
grid.insertBefore(a, bNext);
|
||||
grid.insertBefore(b, aNext);
|
||||
}
|
||||
|
||||
const itemCount = grid.querySelectorAll('[data-custom-item-index]').length;
|
||||
reindexTileDom(a, toIndex, itemCount);
|
||||
reindexTileDom(b, fromIndex, itemCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
function handleAction(action, el, root, state, options, onRefresh) {
|
||||
const opts = getOptions(options);
|
||||
const actions = opts.actions;
|
||||
@@ -371,6 +484,7 @@
|
||||
break;
|
||||
}
|
||||
case actions.removeItem: {
|
||||
if (opts.allowRemove === false) break;
|
||||
const removeIndex = parseInt(el.dataset.itemIndex, 10);
|
||||
if (state.customItems.length <= 1 || Number.isNaN(removeIndex)) break;
|
||||
if (state.customItemEditorIndex === removeIndex) {
|
||||
@@ -403,6 +517,74 @@
|
||||
if (typeof onRefresh === 'function') onRefresh(root);
|
||||
break;
|
||||
}
|
||||
case actions.toggleHide: {
|
||||
const hideIndex = parseInt(el.dataset.itemIndex, 10);
|
||||
if (Number.isNaN(hideIndex) || !state.customItems?.[hideIndex]) break;
|
||||
collectCustomItemsFromForm(root, state, opts);
|
||||
const item = state.customItems[hideIndex];
|
||||
item.hidden = !item.hidden;
|
||||
|
||||
// Remove, then reinsert at the target slot among remaining items
|
||||
state.customItems.splice(hideIndex, 1);
|
||||
let insertAt;
|
||||
if (item.hidden === true) {
|
||||
// Hidden items stay at the end
|
||||
insertAt = state.customItems.length;
|
||||
} else {
|
||||
// Unhide: last active item, immediately before the first still-hidden item
|
||||
insertAt = state.customItems.findIndex((it) => it.hidden === true);
|
||||
if (insertAt < 0) insertAt = state.customItems.length;
|
||||
}
|
||||
state.customItems.splice(insertAt, 0, item);
|
||||
|
||||
if (insertAt !== hideIndex) {
|
||||
moveCustomItemExpanded(state, hideIndex, insertAt);
|
||||
if (state.customItemEditorIndex === hideIndex) {
|
||||
state.customItemEditorIndex = insertAt;
|
||||
} else if (state.customItemEditorIndex != null) {
|
||||
if (hideIndex < state.customItemEditorIndex && insertAt >= state.customItemEditorIndex) {
|
||||
state.customItemEditorIndex -= 1;
|
||||
} else if (hideIndex > state.customItemEditorIndex && insertAt <= state.customItemEditorIndex) {
|
||||
state.customItemEditorIndex += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof onRefresh === 'function') onRefresh(root);
|
||||
break;
|
||||
}
|
||||
case actions.moveLeft: {
|
||||
const leftIndex = parseInt(el.dataset.itemIndex, 10);
|
||||
if (Number.isNaN(leftIndex) || leftIndex <= 0) break;
|
||||
collectCustomItemsFromForm(root, state, opts);
|
||||
const swapWith = leftIndex - 1;
|
||||
const tmp = state.customItems[leftIndex];
|
||||
state.customItems[leftIndex] = state.customItems[swapWith];
|
||||
state.customItems[swapWith] = tmp;
|
||||
swapCustomItemExpanded(state, leftIndex, swapWith);
|
||||
if (state.customItemEditorIndex === leftIndex) state.customItemEditorIndex = swapWith;
|
||||
else if (state.customItemEditorIndex === swapWith) state.customItemEditorIndex = leftIndex;
|
||||
if (!swapAdjacentCustomItemTiles(root, leftIndex, swapWith) && typeof onRefresh === 'function') {
|
||||
onRefresh(root);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case actions.moveRight: {
|
||||
const rightIndex = parseInt(el.dataset.itemIndex, 10);
|
||||
if (Number.isNaN(rightIndex) || rightIndex >= state.customItems.length - 1) break;
|
||||
collectCustomItemsFromForm(root, state, opts);
|
||||
const swapWith = rightIndex + 1;
|
||||
const tmp = state.customItems[rightIndex];
|
||||
state.customItems[rightIndex] = state.customItems[swapWith];
|
||||
state.customItems[swapWith] = tmp;
|
||||
swapCustomItemExpanded(state, rightIndex, swapWith);
|
||||
if (state.customItemEditorIndex === rightIndex) state.customItemEditorIndex = swapWith;
|
||||
else if (state.customItemEditorIndex === swapWith) state.customItemEditorIndex = rightIndex;
|
||||
if (!swapAdjacentCustomItemTiles(root, rightIndex, swapWith) && typeof onRefresh === 'function') {
|
||||
onRefresh(root);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -431,7 +613,10 @@
|
||||
closeEditor: 'hsae-close-custom-item-editor',
|
||||
addItem: 'hsae-add-custom-item',
|
||||
removeItem: 'hsae-remove-custom-item',
|
||||
toggleImages: 'hsae-toggle-custom-images'
|
||||
toggleImages: 'hsae-toggle-custom-images',
|
||||
toggleHide: 'hsae-toggle-custom-item-hide',
|
||||
moveLeft: 'hsae-move-custom-item-left',
|
||||
moveRight: 'hsae-move-custom-item-right'
|
||||
},
|
||||
...overrides
|
||||
})
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
lockVisibility: true,
|
||||
allowGroupChange: false,
|
||||
allowItemSourceChange: false,
|
||||
hideCustomItems: true
|
||||
hideCustomItems: false
|
||||
},
|
||||
postProcessing: {
|
||||
queryFields: ['limit'],
|
||||
@@ -25,7 +25,7 @@
|
||||
lockVisibility: true,
|
||||
allowGroupChange: false,
|
||||
allowItemSourceChange: false,
|
||||
hideCustomItems: true
|
||||
hideCustomItems: false
|
||||
},
|
||||
normal: {
|
||||
queryFields: ['limit', 'sortBy', 'sortOrder'],
|
||||
|
||||
@@ -407,13 +407,34 @@ html:not(.layout-mobile) [data-modal-id="kefin-homescreen-section-create-flow"]
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
[data-modal-id="kefin-homescreen-section-create-flow"] .hsse-custom-item-tile.is-hidden .hsse-custom-item-tile-image,
|
||||
[data-modal-id="kefin-homescreen-section-create-flow"] .hsse-custom-item-tile.is-hidden .hsse-custom-item-tile-label {
|
||||
opacity: 0.4;
|
||||
filter: grayscale(0.35);
|
||||
}
|
||||
|
||||
[data-modal-id="kefin-homescreen-section-create-flow"] .hsse-custom-item-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.2em;
|
||||
}
|
||||
|
||||
[data-modal-id="kefin-homescreen-section-create-flow"] .hsse-custom-item-action,
|
||||
[data-modal-id="kefin-homescreen-section-create-flow"] .hsse-custom-item-trash {
|
||||
font-size: 0;
|
||||
padding: 0.3em;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
[data-modal-id="kefin-homescreen-section-create-flow"] .hsse-custom-item-action:disabled {
|
||||
opacity: 0.35;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
[data-modal-id="kefin-homescreen-section-create-flow"] .hsse-custom-item-action-icon,
|
||||
[data-modal-id="kefin-homescreen-section-create-flow"] .hsse-custom-item-trash-icon {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
@@ -164,6 +164,9 @@
|
||||
}
|
||||
|
||||
function createBlankCustomItem() {
|
||||
if (window.KefinHomeScreenCustomItemsEditor?.createBlankCustomItem) {
|
||||
return window.KefinHomeScreenCustomItemsEditor.createBlankCustomItem();
|
||||
}
|
||||
return {
|
||||
name: '',
|
||||
cardFooter: '',
|
||||
@@ -173,7 +176,8 @@
|
||||
backdropUrl: '',
|
||||
squareUrl: '',
|
||||
bannerUrl: '',
|
||||
logoUrl: ''
|
||||
logoUrl: '',
|
||||
hidden: false
|
||||
};
|
||||
}
|
||||
|
||||
@@ -186,7 +190,10 @@
|
||||
closeEditor: 'wizard-close-custom-item-editor',
|
||||
addItem: 'wizard-add-custom-item',
|
||||
removeItem: 'wizard-remove-custom-item',
|
||||
toggleImages: 'wizard-toggle-custom-images'
|
||||
toggleImages: 'wizard-toggle-custom-images',
|
||||
toggleHide: 'wizard-toggle-custom-item-hide',
|
||||
moveLeft: 'wizard-move-custom-item-left',
|
||||
moveRight: 'wizard-move-custom-item-right'
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -240,6 +247,9 @@
|
||||
}
|
||||
|
||||
function buildStaticItemFromDraft(draft) {
|
||||
if (window.KefinHomeScreenCustomItemsEditor?.buildStaticItemFromDraft) {
|
||||
return window.KefinHomeScreenCustomItemsEditor.buildStaticItemFromDraft(draft);
|
||||
}
|
||||
const name = draft.name?.trim();
|
||||
const posterUrl = draft.posterUrl?.trim();
|
||||
if (!name || !posterUrl) return null;
|
||||
@@ -251,11 +261,13 @@
|
||||
if (value) provided[key] = value;
|
||||
});
|
||||
|
||||
const item = { Name: name, Type: 'Folder' };
|
||||
const item = { Name: name, Type: draft.type || 'Folder' };
|
||||
if (draft.id) item.Id = draft.id;
|
||||
const cardUrl = draft.cardUrl?.trim();
|
||||
if (cardUrl) item.cardUrl = cardUrl;
|
||||
const cardFooter = draft.cardFooter?.trim();
|
||||
if (cardFooter) item.cardFooter = cardFooter;
|
||||
if (draft.hidden === true) item.hidden = true;
|
||||
|
||||
const keys = Object.keys(provided);
|
||||
if (keys.length === 1 && provided.posterUrl) {
|
||||
@@ -4277,7 +4289,10 @@
|
||||
'wizard-close-custom-item-editor',
|
||||
'wizard-add-custom-item',
|
||||
'wizard-remove-custom-item',
|
||||
'wizard-toggle-custom-images'
|
||||
'wizard-toggle-custom-images',
|
||||
'wizard-toggle-custom-item-hide',
|
||||
'wizard-move-custom-item-left',
|
||||
'wizard-move-custom-item-right'
|
||||
]);
|
||||
if (wizardCustomActions.has(action) && window.KefinHomeScreenCustomItemsEditor) {
|
||||
window.KefinHomeScreenCustomItemsEditor.handleAction(
|
||||
|
||||
@@ -1936,7 +1936,8 @@
|
||||
const normalizeTemplate = (value) =>
|
||||
(value || '').replace(/\$\{kefinTweaksRoot\}/g, kefinTweaksRoot).replace(/\$\{serverId\}/g, serverId);
|
||||
|
||||
return sectionConfig.items.map((item, index) => ({
|
||||
const visibleItems = (sectionConfig.items || []).filter((item) => item && item.hidden !== true);
|
||||
return visibleItems.map((item, index) => ({
|
||||
Name: item.Name,
|
||||
Id: item.Id || 'static-' + sectionConfig.id + '-' + index,
|
||||
Type: item.Type || 'Folder',
|
||||
|
||||
@@ -245,7 +245,8 @@
|
||||
.replace(/\$\{kefinTweaksRoot\}/g, kefinTweaksRoot)
|
||||
.replace(/\$\{serverId\}/g, serverId);
|
||||
|
||||
const normalizedItems = sectionConfig.items.map((item, index) => {
|
||||
const visibleItems = sectionConfig.items.filter((item) => item && item.hidden !== true);
|
||||
const normalizedItems = visibleItems.map((item, index) => {
|
||||
const posterUrl = normalizeTemplate(item.posterUrl);
|
||||
const thumbUrl = normalizeTemplate(item.thumbUrl);
|
||||
const squareUrl = normalizeTemplate(item.squareUrl);
|
||||
|
||||
Reference in new issue
Block a user