Update .. workshop search
This commit is contained in:
parent
4baa43bcbf
commit
faf0de39a7
10 changed files with 982 additions and 22 deletions
346
modules/steam_workshop/steam_workshop.js
Normal file
346
modules/steam_workshop/steam_workshop.js
Normal file
|
|
@ -0,0 +1,346 @@
|
|||
(function () {
|
||||
'use strict';
|
||||
|
||||
var Picker = /** @class */ (function () {
|
||||
function Picker(root) {
|
||||
this.root = root;
|
||||
this.endpoint = root.getAttribute('data-endpoint') || '';
|
||||
this.lang = {
|
||||
add: root.getAttribute('data-lang-add') || 'Add',
|
||||
remove: root.getAttribute('data-lang-remove') || 'Remove',
|
||||
loading: root.getAttribute('data-lang-loading') || 'Loading…',
|
||||
error: root.getAttribute('data-lang-error') || 'Something went wrong.',
|
||||
empty: root.getAttribute('data-lang-empty') || 'No results found.',
|
||||
query: root.getAttribute('data-lang-query') || 'Enter a Workshop ID or keyword.',
|
||||
sync: root.getAttribute('data-lang-sync') || 'Sync',
|
||||
};
|
||||
this.selectedInput = root.querySelector('.js-sw-selected-input');
|
||||
this.selectedList = root.querySelector('.js-sw-selected-list');
|
||||
this.resultsBody = root.querySelector('.js-sw-results');
|
||||
this.statusEl = root.querySelector('.js-sw-picker-status');
|
||||
this.searchForm = root.querySelector('.js-sw-search-form');
|
||||
this.searchInput = root.querySelector('.js-sw-search-input');
|
||||
this.searchButton = root.querySelector('.js-sw-search-button');
|
||||
this.state = {
|
||||
selected: this.readInitialSelection(),
|
||||
};
|
||||
this.lastResults = [];
|
||||
this.bindEvents();
|
||||
this.renderSelected();
|
||||
}
|
||||
Picker.prototype.readInitialSelection = function () {
|
||||
if (!this.selectedInput) {
|
||||
return [];
|
||||
}
|
||||
try {
|
||||
var parsed = JSON.parse(this.selectedInput.value || '[]');
|
||||
if (Array.isArray(parsed)) {
|
||||
return parsed.filter(function (item) { return item && item.id; })
|
||||
.map(function (item) { return ({
|
||||
id: String(item.id),
|
||||
label: String(item.label || ('@' + item.id)),
|
||||
author: String(item.author || ''),
|
||||
preview_url: String(item.preview_url || ''),
|
||||
enabled: !(item.enabled === false || item.enabled === 'false' || item.enabled === 0 || item.enabled === '0'),
|
||||
source: String(item.source || 'manual'),
|
||||
}); });
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.warn('Invalid Workshop JSON state', err);
|
||||
}
|
||||
return [];
|
||||
};
|
||||
Picker.prototype.bindEvents = function () {
|
||||
var _this = this;
|
||||
if (this.searchForm && this.searchForm.tagName === 'FORM') {
|
||||
this.searchForm.addEventListener('submit', function (event) {
|
||||
event.preventDefault();
|
||||
_this.performSearch();
|
||||
});
|
||||
}
|
||||
if (this.searchButton) {
|
||||
this.searchButton.addEventListener('click', function (event) {
|
||||
event.preventDefault();
|
||||
_this.performSearch();
|
||||
});
|
||||
}
|
||||
if (this.searchInput && (!this.searchForm || this.searchForm.tagName !== 'FORM')) {
|
||||
this.searchInput.addEventListener('keydown', function (event) {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
_this.performSearch();
|
||||
}
|
||||
});
|
||||
}
|
||||
if (this.selectedList) {
|
||||
this.selectedList.addEventListener('click', function (event) {
|
||||
var target = event.target;
|
||||
if (!(target instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
if (target.matches('.js-sw-remove')) {
|
||||
var id = target.getAttribute('data-id');
|
||||
if (id) {
|
||||
_this.removeSelected(id);
|
||||
}
|
||||
}
|
||||
});
|
||||
this.selectedList.addEventListener('change', function (event) {
|
||||
var target = event.target;
|
||||
if (!(target instanceof HTMLInputElement)) {
|
||||
return;
|
||||
}
|
||||
if (target.matches('.js-sw-toggle')) {
|
||||
var id = target.getAttribute('data-id');
|
||||
if (id) {
|
||||
_this.toggleSelected(id, target.checked);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (this.resultsBody) {
|
||||
this.resultsBody.addEventListener('click', function (event) {
|
||||
var target = event.target;
|
||||
if (!(target instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
if (target.matches('.js-sw-add')) {
|
||||
var payload = target.getAttribute('data-payload');
|
||||
if (payload) {
|
||||
try {
|
||||
var data = JSON.parse(payload);
|
||||
_this.addSelected(data);
|
||||
}
|
||||
catch (err) {
|
||||
console.warn('Invalid payload', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
Picker.prototype.performSearch = function () {
|
||||
var _this = this;
|
||||
if (!this.endpoint || !this.searchInput) {
|
||||
return;
|
||||
}
|
||||
var term = this.searchInput.value.trim();
|
||||
if (!term) {
|
||||
this.setStatus(this.lang.query, 'error');
|
||||
return;
|
||||
}
|
||||
if (this.isSearching) {
|
||||
return;
|
||||
}
|
||||
this.isSearching = true;
|
||||
this.setStatus(this.lang.loading, 'loading');
|
||||
var url = this.endpoint + '&q=' + encodeURIComponent(term);
|
||||
fetch(url, {
|
||||
headers: { 'Accept': 'application/json' },
|
||||
})
|
||||
.then(function (response) {
|
||||
if (!response.ok) {
|
||||
throw new Error('HTTP ' + response.status);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(function (data) {
|
||||
if (!data || data.ok === false) {
|
||||
var message = (data && data.error) ? data.error : _this.lang.error;
|
||||
_this.setStatus(message, 'error');
|
||||
_this.renderResults([]);
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(data.results) && data.results.length) {
|
||||
_this.setStatus('', 'clear');
|
||||
_this.renderResults(data.results);
|
||||
}
|
||||
else {
|
||||
_this.setStatus(_this.lang.empty, 'info');
|
||||
_this.renderResults([]);
|
||||
}
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.error('Workshop search failed', error);
|
||||
_this.setStatus(_this.lang.error, 'error');
|
||||
_this.renderResults([]);
|
||||
})
|
||||
.finally(function () {
|
||||
_this.isSearching = false;
|
||||
});
|
||||
};
|
||||
Picker.prototype.setStatus = function (message, kind) {
|
||||
if (!this.statusEl) {
|
||||
return;
|
||||
}
|
||||
this.statusEl.textContent = message || '';
|
||||
this.statusEl.className = 'sw-picker__status js-sw-picker-status' + (kind ? ' sw-picker__status--' + kind : '');
|
||||
};
|
||||
Picker.prototype.renderResults = function (results) {
|
||||
if (!this.resultsBody) {
|
||||
return;
|
||||
}
|
||||
this.resultsBody.innerHTML = '';
|
||||
if (!Array.isArray(results) || !results.length) {
|
||||
this.lastResults = [];
|
||||
return;
|
||||
}
|
||||
this.lastResults = results.slice();
|
||||
var _loop_1 = function (item) {
|
||||
var normalized = {
|
||||
id: String(item.id),
|
||||
label: String(item.label || ('@' + item.id)),
|
||||
author: String(item.author || ''),
|
||||
preview_url: String(item.preview_url || ''),
|
||||
enabled: true,
|
||||
source: String(item.source || 'search'),
|
||||
};
|
||||
var row = document.createElement('tr');
|
||||
var selectCell = document.createElement('td');
|
||||
var action = document.createElement('button');
|
||||
action.type = 'button';
|
||||
action.className = 'btn btn-xs sw-picker__action js-sw-add';
|
||||
action.textContent = this_1.lang.add;
|
||||
action.setAttribute('data-payload', JSON.stringify(normalized));
|
||||
if (this_1.isSelected(normalized.id)) {
|
||||
action.disabled = true;
|
||||
}
|
||||
selectCell.appendChild(action);
|
||||
var titleCell = document.createElement('td');
|
||||
titleCell.innerHTML = '<strong>' + this_1.escape(normalized.label) + '</strong><div class="sw-picker__result-meta">#' + this_1.escape(normalized.id) + '</div>';
|
||||
var authorCell = document.createElement('td');
|
||||
authorCell.textContent = normalized.author;
|
||||
row.appendChild(selectCell);
|
||||
row.appendChild(titleCell);
|
||||
row.appendChild(authorCell);
|
||||
this_1.resultsBody.appendChild(row);
|
||||
};
|
||||
var this_1 = this;
|
||||
for (var _i = 0, results_1 = results; _i < results_1.length; _i++) {
|
||||
var item = results_1[_i];
|
||||
_loop_1(item);
|
||||
}
|
||||
};
|
||||
Picker.prototype.isSelected = function (id) {
|
||||
return this.state.selected.some(function (item) { return item.id === id; });
|
||||
};
|
||||
Picker.prototype.addSelected = function (item) {
|
||||
if (!item || !item.id || this.isSelected(String(item.id))) {
|
||||
return;
|
||||
}
|
||||
this.state.selected.push({
|
||||
id: String(item.id),
|
||||
label: String(item.label || ('@' + item.id)),
|
||||
author: String(item.author || ''),
|
||||
preview_url: String(item.preview_url || ''),
|
||||
enabled: true,
|
||||
source: String(item.source || 'search'),
|
||||
});
|
||||
this.persist();
|
||||
this.renderSelected();
|
||||
this.renderResults(this.lastResults || []);
|
||||
};
|
||||
Picker.prototype.removeSelected = function (id) {
|
||||
var next = this.state.selected.filter(function (item) { return item.id !== id; });
|
||||
this.state.selected = next;
|
||||
this.persist();
|
||||
this.renderSelected();
|
||||
this.renderResults(this.lastResults || []);
|
||||
};
|
||||
Picker.prototype.toggleSelected = function (id, enabled) {
|
||||
var changed = false;
|
||||
this.state.selected = this.state.selected.map(function (item) {
|
||||
if (item.id === id) {
|
||||
changed = true;
|
||||
return {
|
||||
id: item.id,
|
||||
label: item.label,
|
||||
author: item.author,
|
||||
preview_url: item.preview_url,
|
||||
enabled: enabled,
|
||||
source: item.source,
|
||||
};
|
||||
}
|
||||
return item;
|
||||
});
|
||||
if (changed) {
|
||||
this.persist();
|
||||
}
|
||||
};
|
||||
Picker.prototype.renderSelected = function () {
|
||||
if (!this.selectedList) {
|
||||
return;
|
||||
}
|
||||
this.selectedList.innerHTML = '';
|
||||
if (!this.state.selected.length) {
|
||||
var emptyText = this.selectedList.getAttribute('data-empty-text') || '';
|
||||
if (emptyText) {
|
||||
var empty = document.createElement('div');
|
||||
empty.className = 'sw-picker__empty';
|
||||
empty.textContent = emptyText;
|
||||
this.selectedList.appendChild(empty);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var this_2 = this;
|
||||
for (var _i = 0, _a = this.state.selected; _i < _a.length; _i++) {
|
||||
var item = _a[_i];
|
||||
var chip = document.createElement('div');
|
||||
chip.className = 'sw-picker__chip';
|
||||
chip.innerHTML = '<div class="sw-picker__chip-text"><strong>' + this.escape(item.label) + '</strong><span>#' + this.escape(item.id) + '</span></div>';
|
||||
var controls = document.createElement('div');
|
||||
controls.className = 'sw-picker__chip-controls';
|
||||
var toggle = document.createElement('label');
|
||||
toggle.className = 'sw-picker__toggle';
|
||||
var checkbox = document.createElement('input');
|
||||
checkbox.type = 'checkbox';
|
||||
checkbox.className = 'js-sw-toggle';
|
||||
checkbox.checked = item.enabled !== false;
|
||||
checkbox.setAttribute('data-id', item.id);
|
||||
toggle.appendChild(checkbox);
|
||||
var toggleText = document.createElement('span');
|
||||
toggleText.textContent = this_2.lang.sync;
|
||||
toggle.appendChild(toggleText);
|
||||
var removeBtn = document.createElement('button');
|
||||
removeBtn.type = 'button';
|
||||
removeBtn.className = 'sw-picker__chip-remove js-sw-remove';
|
||||
removeBtn.setAttribute('data-id', item.id);
|
||||
removeBtn.textContent = this_2.lang.remove;
|
||||
controls.appendChild(toggle);
|
||||
controls.appendChild(removeBtn);
|
||||
chip.appendChild(controls);
|
||||
this.selectedList.appendChild(chip);
|
||||
}
|
||||
this.persist();
|
||||
};
|
||||
Picker.prototype.persist = function () {
|
||||
if (!this.selectedInput) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this.selectedInput.value = JSON.stringify(this.state.selected);
|
||||
}
|
||||
catch (err) {
|
||||
console.error('Unable to serialize workshop selection', err);
|
||||
}
|
||||
};
|
||||
Picker.prototype.escape = function (value) {
|
||||
var div = document.createElement('div');
|
||||
div.textContent = value;
|
||||
return div.innerHTML;
|
||||
};
|
||||
return Picker;
|
||||
}());
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var nodes = document.querySelectorAll('.sw-picker');
|
||||
Array.prototype.forEach.call(nodes, function (node) {
|
||||
try {
|
||||
new Picker(node);
|
||||
}
|
||||
catch (err) {
|
||||
console.error('Failed to boot Steam Workshop picker', err);
|
||||
}
|
||||
});
|
||||
});
|
||||
})();
|
||||
Loading…
Add table
Add a link
Reference in a new issue