diff --git a/plugins/core-altered-cards/assets/card-search.js b/plugins/core-altered-cards/assets/card-search.js index 7f64625..9a18f4e 100644 --- a/plugins/core-altered-cards/assets/card-search.js +++ b/plugins/core-altered-cards/assets/card-search.js @@ -108,6 +108,12 @@ function CardSearch(cfg) { forest: ini.forest !== undefined ? ini.forest : null, mountain: ini.mountain !== undefined ? ini.mountain : null, ocean: ini.ocean !== undefined ? ini.ocean : null, + mainCostOp: 'eq', + recallCostOp: 'eq', + forestOp: 'eq', + mountainOp: 'eq', + oceanOp: 'eq', + costRelation: '', sets: (_setsDirty ? (ini.sets || []) : DEFAULT_SETS.slice()), subtypes: (ini.subtypes || []).slice(), keywords: (ini.keywords || []).slice(), @@ -117,6 +123,8 @@ function CardSearch(cfg) { isErrated: !!ini.isErrated, isSuspended: !!ini.isSuspended, hasNoEffect: !!ini.hasNoEffect, + effects: [], + effectMode: 'or', sort: ini.sort || DEFAULT_SORT_1, }; @@ -127,6 +135,9 @@ function CardSearch(cfg) { var tsInst = {}; var _collEl = null; var _defaultCollection = ''; + var _effectData = null; + var _effectLoading = false; + var _effectQueue = []; // sort key → API order params var SORT_MAP = { @@ -252,6 +263,219 @@ function CardSearch(cfg) { return s || ' '; } + // ── Effect filter ──────────────────────────────────────────────────────── + + function _loadEffectData(cb) { + if (_effectData) { cb(_effectData); return; } + _effectQueue.push(cb); + if (_effectLoading) return; + _effectLoading = true; + var done = 0; + var res = { triggers: [], conditions: [], effects: [] }; + ['triggers', 'conditions', 'effects'].forEach(function(key) { + fetch(API_BASE + '/api/' + key) + .then(function(r) { return r.json(); }) + .then(function(data) { res[key] = Array.isArray(data) ? data : []; }) + .catch(function() {}) + .then(function() { + done++; + if (done < 3) return; + _effectData = res; + _effectLoading = false; + _effectQueue.forEach(function(fn) { fn(_effectData); }); + _effectQueue = []; + }); + }); + } + + function _effectLabel(item) { + var t = item.translations || {}; + return t[LANG] || t.en || String(item.alteredId); + } + + function _effectLabelPlain(item) { + return _effectLabel(item).replace(/\{[^}]*\}/g, '').replace(/\s+/g, ' ').trim() || String(item.alteredId); + } + + function _alteredIconHtml(escaped) { + return escaped.replace(/\{([^}]+)\}/g, function(_, code) { + return ''; + }); + } + + function _buildEffectSel(items, anyLabel, currentVal) { + var sorted = items.slice().sort(function(a, b) { + return _effectLabelPlain(a).localeCompare(_effectLabelPlain(b)); + }); + var sel = document.createElement('select'); + var def = document.createElement('option'); + def.value = ''; def.textContent = anyLabel; sel.appendChild(def); + sorted.forEach(function(item) { + var o = document.createElement('option'); + o.value = String(item.alteredId); + o.textContent = _effectLabel(item); + if (String(item.alteredId) === String(currentVal)) o.selected = true; + sel.appendChild(o); + }); + return sel; + } + + function _buildEffectRow(n, data, saved) { + saved = saved || {}; + var rowEl = document.createElement('div'); + rowEl.className = 'effect-row d-flex gap-1 align-items-center mb-1'; + rowEl.dataset.effectN = n; + + var sels = [ + _buildEffectSel(data.triggers, txt.any_trigger || '—', saved.trigger), + _buildEffectSel(data.conditions, txt.any_condition || '—', saved.condition), + _buildEffectSel(data.effects, txt.any_effect || '—', saved.effect), + ]; + + var tsRender = typeof TomSelect !== 'undefined' ? { + option: function(d, e) { + return '
' + _alteredIconHtml(e(d.text)) + '
'; + }, + item: function(d, e) { + return '
' + _alteredIconHtml(e(d.text)) + '
'; + } + } : null; + + var tsInsts = []; + sels.forEach(function(sel) { + var wrap = document.createElement('div'); + wrap.style.cssText = 'flex:1;min-width:160px'; + wrap.appendChild(sel); + rowEl.appendChild(wrap); + if (tsRender) { + var ts = new TomSelect(sel, { + create: false, maxItems: 1, plugins: [], + onChange: updateFilterCount, + render: tsRender, + }); + tsInsts.push(ts); + } else { + sel.addEventListener('change', updateFilterCount); + tsInsts.push(null); + } + }); + rowEl._tsInsts = tsInsts; + + var rmBtn = document.createElement('button'); + rmBtn.type = 'button'; + rmBtn.className = 'btn btn-sm btn-outline-secondary flex-shrink-0'; + rmBtn.innerHTML = ''; + rmBtn.addEventListener('click', function() { _removeEffectRow(rowEl); }); + rowEl.appendChild(rmBtn); + return rowEl; + } + + function _syncEffectUi() { + var rowsEl = document.getElementById(P + '-effect-rows'); + var addBtn = document.getElementById(P + '-effect-add'); + var modeEl = document.getElementById(P + '-effect-mode'); + var count = rowsEl ? rowsEl.querySelectorAll('.effect-row').length : 0; + if (rowsEl) { + rowsEl.querySelectorAll('.effect-row').forEach(function(r) { + var rm = r.querySelector('button'); + if (rm) rm.style.display = count > 1 ? '' : 'none'; + }); + } + if (addBtn) addBtn.style.display = count >= 3 ? 'none' : ''; + if (modeEl) modeEl.style.display = count > 1 ? '' : 'none'; + } + + function _removeEffectRow(rowEl) { + if (rowEl._tsInsts) rowEl._tsInsts.forEach(function(ts) { if (ts) ts.destroy(); }); + if (rowEl.parentNode) rowEl.parentNode.removeChild(rowEl); + _syncEffectUi(); + updateFilterCount(); + } + + function _addEffectRow(saved) { + var rowsEl = document.getElementById(P + '-effect-rows'); + if (!rowsEl) return; + var count = rowsEl.querySelectorAll('.effect-row').length; + if (count >= 3) return; + var n = count; + if (_effectData) { + rowsEl.appendChild(_buildEffectRow(n, _effectData, saved)); + _syncEffectUi(); + } else { + var ph = document.createElement('div'); + ph.className = 'effect-row text-muted small py-1'; + ph.dataset.effectN = n; + ph.textContent = '…'; + rowsEl.appendChild(ph); + _syncEffectUi(); + _loadEffectData(function(data) { + var real = _buildEffectRow(n, data, saved); + if (ph.parentNode) ph.parentNode.replaceChild(real, ph); + _syncEffectUi(); + }); + } + } + + function _readEffectRows() { + var rowsEl = document.getElementById(P + '-effect-rows'); + var modeEl = document.getElementById(P + '-effect-mode'); + filters.effects = []; + filters.effectMode = modeEl ? (modeEl.dataset.mode || 'or') : 'or'; + if (!rowsEl) return; + rowsEl.querySelectorAll('.effect-row').forEach(function(row) { + var insts = row._tsInsts; + if (insts && insts.length >= 3) { + filters.effects.push({ + trigger: insts[0] ? String(insts[0].getValue() || '') : '', + condition: insts[1] ? String(insts[1].getValue() || '') : '', + effect: insts[2] ? String(insts[2].getValue() || '') : '', + }); + } else { + var sels = row.querySelectorAll('select'); + if (sels.length >= 3) { + filters.effects.push({ trigger: sels[0].value, condition: sels[1].value, effect: sels[2].value }); + } + } + }); + } + + function _resetEffectRows() { + var rowsEl = document.getElementById(P + '-effect-rows'); + var modeEl = document.getElementById(P + '-effect-mode'); + if (rowsEl) { rowsEl.innerHTML = ''; _effectRowsCnt = 0; } + if (modeEl) { + modeEl.style.display = 'none'; + modeEl.dataset.mode = 'or'; + modeEl.querySelectorAll('.effect-mode-btn').forEach(function(b) { + b.classList.toggle('active', b.dataset.mode === 'or'); + }); + } + _addEffectRow(null); + } + + function initEffects() { + var addBtn = document.getElementById(P + '-effect-add'); + var modeEl = document.getElementById(P + '-effect-mode'); + if (addBtn) { + addBtn.addEventListener('click', function() { _addEffectRow(null); }); + } + if (modeEl) { + modeEl.addEventListener('click', function(e) { + var btn = e.target.closest('.effect-mode-btn'); + if (!btn) return; + var mode = btn.dataset.mode; + modeEl.dataset.mode = mode; + modeEl.querySelectorAll('.effect-mode-btn').forEach(function(b) { + b.classList.toggle('active', b.dataset.mode === mode); + }); + filters.effectMode = mode; + }); + } + _addEffectRow(null); + } + + // ── End effect filter ──────────────────────────────────────────────────── + // build direct API URL function buildApiUrl(page) { // Reference lookup: bypass all filters and collection scope @@ -305,17 +529,38 @@ function CardSearch(cfg) { filters.subtypes.forEach(function(v) { parts.push('subTypes[]=' + encodeURIComponent(v)); }); filters.variations.forEach(function(v) { parts.push('variation[]=' + encodeURIComponent(v)); }); - if (filters.mainCost !== null) parts.push('mainCost[]=' + filters.mainCost); - if (filters.recallCost !== null) parts.push('recallCost[]=' + filters.recallCost); - if (filters.forest !== null) parts.push('forestPower[]=' + filters.forest); - if (filters.mountain !== null) parts.push('mountainPower[]=' + filters.mountain); - if (filters.ocean !== null) parts.push('oceanPower[]=' + filters.ocean); - + function _costP(field, val, op) { + if (val === null) return; + var suffix = op === 'lt' ? '[lt]' : op === 'lte' ? '[lte]' : op === 'gt' ? '[gt]' : op === 'gte' ? '[gte]' : ''; + parts.push(field + suffix + '=' + val); + } + _costP('mainCost', filters.mainCost, filters.mainCostOp); + _costP('recallCost', filters.recallCost, filters.recallCostOp); + _costP('forestPower', filters.forest, filters.forestOp); + _costP('mountainPower',filters.mountain, filters.mountainOp); + _costP('oceanPower', filters.ocean, filters.oceanOp); + if (filters.costRelation === 'eq') parts.push('costRelation=equal'); + else if (filters.costRelation === 'main_gt') parts.push('costRelation=mainHigher'); + else if (filters.costRelation === 'recall_gt') parts.push('costRelation=recallHigher'); if (filters.isBanned) parts.push('isBanned=true'); if (filters.isErrated) parts.push('isErrated=true'); if (filters.isSuspended) parts.push('isSuspended=true'); if (filters.hasNoEffect) parts.push('hasNoEffect=true'); + _readEffectRows(); + var _activeEffects = filters.effects.filter(function(ef) { + return ef.trigger || ef.condition || ef.effect; + }); + if (_activeEffects.length) { + if (_activeEffects.length > 1) parts.push('effectSlotMode=' + filters.effectMode); + _activeEffects.forEach(function(ef, i) { + var n = i; + if (ef.trigger) parts.push('effectSlot[' + n + '][trigger]=' + encodeURIComponent(ef.trigger)); + if (ef.condition) parts.push('effectSlot[' + n + '][condition]=' + encodeURIComponent(ef.condition)); + if (ef.effect) parts.push('effectSlot[' + n + '][effect]=' + encodeURIComponent(ef.effect)); + }); + } + if (filters.q) parts.push('name=' + encodeURIComponent(filters.q)); return API_BASE + '/api/cards?' + parts.join('&'); @@ -394,11 +639,15 @@ function CardSearch(cfg) { filters.subtypes = sv('subtype'); filters.keywords = sv('keyword'); filters.variations = sv('variation'); - filters.mainCost = rv('maincost'); - filters.recallCost = rv('recallcost'); - filters.forest = rv('forestpower'); - filters.mountain = rv('mountainpower'); - filters.ocean = rv('oceanpower'); + function rop(id) { + var el = document.getElementById(P + '-filter-' + id + '-op'); + return el ? (el.value || 'eq') : 'eq'; + } + filters.mainCost = rv('maincost'); filters.mainCostOp = rop('maincost'); + filters.recallCost = rv('recallcost'); filters.recallCostOp = rop('recallcost'); + filters.forest = rv('forestpower'); filters.forestOp = rop('forestpower'); + filters.mountain = rv('mountainpower'); filters.mountainOp = rop('mountainpower'); + filters.ocean = rv('oceanpower'); filters.oceanOp = rop('oceanpower'); var statusVals = sv('status'); filters.isBanned = statusVals.indexOf('banned') >= 0; filters.isErrated = statusVals.indexOf('errated') >= 0; @@ -407,6 +656,9 @@ function CardSearch(cfg) { filters.hasNoEffect = hneEl ? hneEl.checked : false; var kwModeEl = document.getElementById(P + '-kw-mode'); filters.keywordMode = kwModeEl ? (kwModeEl.dataset.mode || 'or') : 'or'; + var _crEl = document.getElementById(P + '-filter-cost-relation'); + filters.costRelation = _crEl ? _crEl.value : ''; + _readEffectRows(); } // filter count badge @@ -423,7 +675,9 @@ function CardSearch(cfg) { + (_setsDirty ? filters.sets.length : 0) + filters.subtypes.length + filters.keywords.length + (_variationDirty ? filters.variations.length : 0) + (_collDirty ? 1 : 0) + (filters.isBanned ? 1 : 0) + (filters.isErrated ? 1 : 0) + (filters.isSuspended ? 1 : 0) - + (filters.hasNoEffect ? 1 : 0); + + (filters.hasNoEffect ? 1 : 0) + + (filters.costRelation ? 1 : 0) + + filters.effects.filter(function(ef) { return ef.trigger || ef.condition || ef.effect; }).length; if (elFilterCount) { elFilterCount.textContent = n || ''; elFilterCount.style.display = n > 0 ? '' : 'none'; @@ -790,6 +1044,11 @@ function CardSearch(cfg) { filters.isErrated = false; filters.isSuspended = false; filters.hasNoEffect = false; + filters.effects = []; + filters.effectMode = 'or'; + filters.mainCostOp = 'eq'; filters.recallCostOp = 'eq'; + filters.forestOp = 'eq'; filters.mountainOp = 'eq'; filters.oceanOp = 'eq'; + filters.costRelation = ''; filters.sort = DEFAULT_SORT_1; if (elSearch) elSearch.value = ''; @@ -813,6 +1072,15 @@ function CardSearch(cfg) { }); var _hneReset = document.getElementById(P + '-filter-hasnoeffect'); if (_hneReset) _hneReset.checked = false; + ['maincost','recallcost','forestpower','mountainpower','oceanpower'].forEach(function(id) { + var opEl = document.getElementById(P + '-filter-' + id + '-op'); + if (opEl) opEl.value = 'eq'; + var valEl = document.getElementById(P + '-filter-' + id); + if (valEl) valEl.value = ''; + }); + var _crReset = document.getElementById(P + '-filter-cost-relation'); + if (_crReset) _crReset.value = ''; + _resetEffectRows(); var _kwReset = document.getElementById(P + '-kw-mode'); if (_kwReset) { _kwReset.dataset.mode = 'or'; @@ -936,10 +1204,12 @@ function CardSearch(cfg) { var userInit = (opts || {}).onInitialize; var merged = Object.assign({ plugins: ['remove_button'], create: false, maxOptions: null }, opts || {}); merged.onInitialize = function() { - this.control_input.style.cssText = - 'width:0!important;min-width:0!important;padding:0!important;margin:0!important;opacity:0!important;flex:0 0 0!important;'; - this.control_input.setAttribute('inputmode', 'none'); - this.control_input.readOnly = true; + if (!this.settings.placeholder) { + this.control_input.style.cssText = + 'width:0!important;min-width:0!important;padding:0!important;margin:0!important;opacity:0!important;flex:0 0 0!important;'; + this.control_input.setAttribute('inputmode', 'none'); + this.control_input.readOnly = true; + } if (userInit) userInit.call(this); }; var inst = new TomSelect('#' + id, merged); @@ -1028,8 +1298,8 @@ function CardSearch(cfg) { }); } - tsInst.subtype = makeTs('subtype', { options: opts.subtypeOptions || [], items: opts.initialSubtypes || [] }); - tsInst.keyword = makeTs('keyword', { options: opts.keywordOptions || [], items: opts.initialKeywords || [] }); + tsInst.subtype = makeTs('subtype', { options: opts.subtypeOptions || [], items: opts.initialSubtypes || [], placeholder: txt.lbl_subtype || 'Subtype' }); + tsInst.keyword = makeTs('keyword', { options: opts.keywordOptions || [], items: opts.initialKeywords || [], placeholder: txt.lbl_keyword || 'Keyword' }); tsInst.variation = makeTs('variation', { options: opts.variationOptions || [], onChange: function() { _variationDirty = true; updateFilterCount(); } }); var _initVars = (opts.initialVariations && opts.initialVariations.length) ? opts.initialVariations : DEFAULT_VARIATIONS.slice(); if (tsInst.variation && _initVars.length) tsInst.variation.setValue(_initVars, true); @@ -1045,7 +1315,7 @@ function CardSearch(cfg) { }); }); }); - tsInst.status = makeTs('status', {}); + tsInst.status = makeTs('status', { placeholder: txt.lbl_card_status || 'Status' }); // Range selects: set initial values and register change listeners var _iniRange = cfg.initial || {}; @@ -1087,6 +1357,16 @@ function CardSearch(cfg) { if (_iniRange.hasNoEffect) _hneEl.checked = true; _hneEl.addEventListener('change', updateFilterCount); } + + // Cost operator selects + cost relation + ['maincost','recallcost','forestpower','mountainpower','oceanpower'].forEach(function(id) { + var el = document.getElementById(P + '-filter-' + id + '-op'); + if (el) el.addEventListener('change', updateFilterCount); + }); + var _crEl2 = document.getElementById(P + '-filter-cost-relation'); + if (_crEl2) _crEl2.addEventListener('change', updateFilterCount); + + initEffects(); } // wire up events diff --git a/plugins/core-altered-cards/includes/card-search.php b/plugins/core-altered-cards/includes/card-search.php index c8dbb34..abf2d60 100644 --- a/plugins/core-altered-cards/includes/card-search.php +++ b/plugins/core-altered-cards/includes/card-search.php @@ -1,27 +1,7 @@ = 3 ? 2 : $_csDefCols; -$_csSorts = $_csTxt['sorts'] ?? []; +$_csSorts = $_csTxt['sorts'] ?? []; $_csValidCost = array_map('strval', range(0, 12)); -// Derived labels $_csFactionNames = []; foreach ($_csFactions as $_fk => $_fv) { $_csFactionNames[$_fk] = $_fv[$_csLang] ?? $_fv['en'] ?? $_fk; @@ -73,10 +52,9 @@ $_csRarityGems = []; foreach ($_csRarities as $_rk => $_rv) { $_csRarityTxt[$_rk] = $_rv[$_csLang] ?? $_rv['en'] ?? $_rk; - $_csRarityGems[$_rk] = $_rv['gem'] ?? substr($_rk, 0, 1); + $_csRarityGems[$_rk] = $_rv['gem'] ?? substr($_rk, 0, 1); } -// Collection filter options by type $_csCollOpts = []; $_csSeenColl = []; $_csDefCollection = $_csDef['collection'] ?? 'official'; @@ -88,25 +66,59 @@ } } $_csHasCollFilter = !empty($_csCollOpts); + +$_csOfficialSets = array_filter($_csSets, fn($s) => ($s['type'] ?? '') === 'official' && ($s['subtype'] ?? '') === 'main'); + +$_csRangeFields = [ + 'maincost' => ['icon' => '', 'title' => $_csTxt['lbl_cost_m'] ?? 'Hand'], + 'recallcost' => ['icon' => '', 'title' => $_csTxt['lbl_cost_r'] ?? 'Reserve'], + 'forestpower' => ['icon' => '', 'title' => $_csTxt['lbl_forest'] ?? 'Forest'], + 'mountainpower' => ['icon' => '', 'title' => $_csTxt['lbl_mountain'] ?? 'Mountain'], + 'oceanpower' => ['icon' => '', 'title' => $_csTxt['lbl_ocean'] ?? 'Ocean'], +]; + +$_csOpEq = $_csLang === 'fr' ? 'égal' : 'equal'; +$_csOpLt = $_csLang === 'fr' ? 'inférieur' : 'less than'; +$_csOpGt = $_csLang === 'fr' ? 'supérieur' : 'greater than'; +$_csHeroLabel = $_csTxt['lbl_hero'] ?? ($_csLang === 'fr' ? 'Héros' : 'Hero'); +$_csHeroAll = $_csTxt['hero_all'] ?? ($_csLang === 'fr' ? 'Tous les héros' : 'All heroes'); ?> + + + +
- -
- - + + + +
+ + +
+ +
- - -
+ +
+ +
+
+ + +
+
+ +
+
+
+ +
+
+ +
+
+ + + +
$_sv): ?>
- - -
+ + +
$_fv): ?> -
- - - - -
+ + + $_rv): ?> + + +
+ +
+
- - -
- - -