props/Props/assets/js/specific/search.js

300 lines
15 KiB
JavaScript

import { apiHttp } from "../services/http";
import Alpine from "alpinejs";
import clone from "just-clone";
const UPLOAD_DELAY = 1500;
const START_SLIDE = "#quick-picks-slide";
function initInteractiveElements() {
let configurationToggle = document.getElementById("configuration-toggle");
let configurationElem = document.getElementById("configuration");
configurationElem.addEventListener("show.bs.collapse", function () {
configurationToggle.classList.add("active");
});
configurationElem.addEventListener("hidden.bs.collapse", function () {
configurationToggle.classList.remove("active");
});
}
function initConfigData() {
document.addEventListener("alpine:init", () => {
Alpine.data("search", () => ({
loggedIn: false,
query: "",
searchOutline: {
ready: false,
filters: {
"currency": 0,
"minRating": 80,
"keepUnrated": true,
"enableUpperPrice": false,
"upperPrice": 0,
"lowerPrice": 0,
"minPurchases": 0,
"keepUnknownPurchaseCount": true,
"minReviews": 0,
"keepUnknownReviewCount": true,
"enableMaxShipping": false,
"maxShippingFee": 0,
"keepUnknownShipping": true
},
shopToggles: {},
},
deletingSearchOutline: false,
creatingSearchOutline: false,
updatingLastUsed: false,
changingName: false,
updatingFilters: false,
updatingDisabledShops: false,
searchOutlines: [],
selectedSearchOutline: 0,
serverSearchOutlineName: null,
resultsQuery: null,
results: {
bestPrice: null,
},
searchOutlineChangeTimeout: null,
timeoutInProgress: false,
hasResults() {
return this.resultsQuery !== null;
},
submitSearch() {
// TODO: implement search Web API call.
this.resultsQuery = this.query;
console.log("Search requested.");
},
SearchOutlineNameChange() {
if (this.validateSearchOutlineName()) {
this.changeSearchOutlineName(this.serverSearchOutlineName, this.searchOutlines[this.selectedSearchOutline]);
}
},
validateAllNumericalInputs() {
if (!this.searchOutline.filters.lowerPrice) this.searchOutline.filters.lowerPrice = 0;
if (!this.searchOutline.filters.upperPrice) this.searchOutline.filters.upperPrice = 0;
if (!this.searchOutline.filters.maxShippingFee) this.searchOutline.filters.maxShippingFee = 0;
if (!this.searchOutline.filters.minPurchases) this.searchOutline.filters.minPurchases = 0;
if (!this.searchOutline.filters.minRating) this.searchOutline.filters.minRating = 0;
if (!this.searchOutline.filters.minReviews) this.searchOutline.filters.minReviews = 0;
},
validateSearchOutlineName() {
let clonedSearchOutlines = this.searchOutlines.slice();
clonedSearchOutlines.splice(this.selectedSearchOutline, 1);
if (this.searchOutlines[this.selectedSearchOutline].length < 1 || clonedSearchOutlines.includes(this.searchOutlines[this.selectedSearchOutline])) {
this.searchOutlines[this.selectedSearchOutline] = this.serverSearchOutlineName;
return false;
}
return true;
},
searchOutlineChanged() {
if (!this.loggedIn) return;
if (this.searchOutlineChangeTimeout != null) {
clearTimeout(this.searchOutlineChangeTimeout);
}
this.timeoutInProgress = true;
this.searchOutlineChangeTimeout = setTimeout(() => {
this.uploadAll();
this.timeoutInProgress = false;
}, UPLOAD_DELAY);
},
uploadAll() {
let name = this.searchOutlines[this.selectedSearchOutline];
this.uploadFilters(name, clone(this.searchOutline.filters));
this.uploadDisabledShops(name, clone(this.searchOutline.shopToggles));
},
async uploadFilters(name, filters) {
if (!this.loggedIn) return;
this.validateAllNumericalInputs();
this.updatingFilters = true;
let uploadFilterResponse = await apiHttp.put(`SearchOutline/${name}/Filters`, filters);
this.updatingFilters = false;
if (uploadFilterResponse.status != 204) {
throw `Error while attempting to upload filters. Response code ${uploadFilterResponse.status} (expected 204).`;
}
},
async uploadDisabledShops(name, disabledShops) {
if (!this.loggedIn) return;
this.updatingDisabledShops = true;
let disabledShopSet = [];
Object.keys(disabledShops).forEach(key => {
if (!this.searchOutline.shopToggles[key]) {
disabledShopSet.push(key);
}
});
let uploadDisabledShopsResponse = await apiHttp.put(`SearchOutline/${name}/DisabledShops`, disabledShopSet);
this.updatingDisabledShops = false;
if (uploadDisabledShopsResponse.status != 204) {
throw `Error while attempting to upload disabled shops. Response code ${uploadDisabledShopsResponse.status} (expected 204).`;
}
},
async createSearchOutline(name) {
if (!this.loggedIn) return;
this.creatingSearchOutline = true;
let createRequest = await apiHttp.post("SearchOutline/" + name);
this.creatingSearchOutline = false;
if (createRequest.status != 204) {
throw `Could not create profile. Response code ${createRequest.status} (expected 204).`;
}
this.searchOutlines.push(name);
},
async updateLastUsed(name) {
if (!this.loggedIn) return;
this.updatingLastUsed = true;
let lastUsedRequest = await apiHttp.put("SearchOutline/" + name + "/LastUsed");
this.updatingLastUsed = false;
if (lastUsedRequest.status != 204) {
throw `Could not update last used search outline. Received status code ${lastUsedRequest.status} (expected 204).`;
}
},
async changeSearchOutlineName(old, current) {
if (!this.loggedIn) return;
this.changingName = true;
let nameChangeRequest = await apiHttp.put(`SearchOutline/${old}/Name/${current}`);
this.changingName = false;
if (nameChangeRequest.status != 204) {
throw `Could not update name on server side. Received ${nameChangeRequest.status} (expected 204).`;
}
this.serverSearchOutlineName = current;
},
async loadSearchOutline(name) {
this.searchOutline.ready = false;
if (!this.loggedIn) {
let defaultNameRequest = await apiHttp.get("SearchOutline/DefaultName");
if (defaultNameRequest.status != 200) {
console.error(`Could not load default search outline name. Got response code ${defaultNameRequest.status} (Expected 200).`);
return;
}
this.searchOutlines.push(defaultNameRequest.data);
let disabledShopsResponse = await apiHttp.get("SearchOutline/DefaultDisabledShops");
let availableShops = (await apiHttp.get("Search/AvailableShops")).data;
if (disabledShopsResponse.status == 200) {
availableShops.forEach(shopName => {
this.searchOutline.shopToggles[shopName] = !disabledShopsResponse.data.includes(shopName);
});
} else {
console.error(`Could not fetch default disabled shops for "${name}". Status code: ${disabledShopsResponse.status} (Expected 200)`);
return;
}
} else {
if (this.searchOutlineChangeTimeout != null) {
clearTimeout(this.searchOutlineChangeTimeout);
this.uploadAll();
}
let filterResponse = await apiHttp.get("SearchOutline/" + name + "/Filters");
if (filterResponse.status == 200) {
this.searchOutline.filters = filterResponse.data;
} else {
console.error(`Could not fetch filter for "${name}". Status code: ${filterResponse.status} (Expected 200)`);
return;
}
let disabledShopsResponse = await apiHttp.get("SearchOutline/" + name + "/DisabledShops");
let availableShops = (await apiHttp.get("Search/AvailableShops")).data;
if (disabledShopsResponse.status == 200) {
availableShops.forEach(shopName => {
this.searchOutline.shopToggles[shopName] = !disabledShopsResponse.data.includes(shopName);
});
} else {
console.error(`Could not fetch disabled shops for "${name}". Status code: ${disabledShopsResponse.status} (Expected 200)`);
return;
}
await this.updateLastUsed(name);
}
this.serverSearchOutlineName = name;
this.searchOutline.ready = true;
},
createSearchOutlineWithGeneratedName() {
apiHttp.get("SearchOutline/DefaultName/").then((response) => {
if (response.status != 200) {
throw `Could not get a default name. Response code ${response.status} (expected 200).`;
}
this.createSearchOutline(response.data);
});
},
deleteSearchOutline(name) {
this.deletingSearchOutline = true;
let beforeDelete = this.searchOutlines[this.selectedSearchOutline];
if (this.selectedSearchOutline == this.searchOutlines.length - 1 && this.searchOutlines.indexOf(name) <= this.selectedSearchOutline) {
this.selectedSearchOutline -= 1;
}
apiHttp.delete(`SearchOutline/${name}`).then((results) => {
this.deletingSearchOutline = false;
if (results.status != 204) {
throw `Unable to delete ${name}. Received status ${results.status} (expected 204)`;
}
this.searchOutlines.splice(this.searchOutlines.indexOf(name), 1);
if (beforeDelete !== this.searchOutlines[this.selectedSearchOutline]) {
this.loadSearchOutline(this.searchOutlines[this.selectedSearchOutline]).then(() => {
this.deletingSearchOutline = false;
});
} else {
this.deletingSearchOutline = false;
}
});
},
async init() {
// TODO: Test logged in outline sequence and logged out outline sequence.
this.loggedIn = (await apiHttp.get("User/LoggedIn")).data;
if (this.loggedIn) {
this.searchOutlines = (await apiHttp.get("SearchOutline/Names")).data;
if (this.searchOutlines.length == 0) {
let name = (await apiHttp.get("SearchOutline/DefaultName")).data;
try {
await this.createSearchOutline(name);
await this.updateLastUsed(name);
} catch (error) {
console.error(error);
return;
}
} else {
let lastUsedRequest = await apiHttp.get("SearchOutline/LastUsed");
if (lastUsedRequest.status == 200) {
this.selectedSearchOutline = this.searchOutlines.indexOf(lastUsedRequest.data);
} else {
console.warn(`Could not load name of last used search outline. Got response code ${lastUsedRequest.status} (Expected 200). Using "${this.searchOutlines[0]}".`);
let putlastUsedRequest = await apiHttp.put("SearchOutline/" + this.searchOutlines[this.selectedSearchOutline] + "/LastUsed");
if (putlastUsedRequest.status != 204) {
console.error(`Could not update last used search outline. Received status code ${putlastUsedRequest.status} (Expected 204).`);
return;
}
}
}
}
this.loadSearchOutline(this.searchOutlines[this.selectedSearchOutline]);
}
}));
});
Alpine.start();
}
function initSlides() {
document.querySelectorAll("#content-pages > .selectors > .nav-item > button").forEach(tabElem => {
tabElem.addEventListener("click", () => {
const destUrl = new URL(tabElem.getAttribute("data-bs-target"), window.location.href);
if (location.href === destUrl.href) return;
history.pushState({}, document.title, destUrl);
});
});
const goTo = () => {
const match = location.href.match("(#[\\w-]+)");
const idAnchor = match && match[1] ? match[1] : START_SLIDE;
document.querySelector("#content-pages > .selectors > .nav-item > .active")?.classList.remove("active");
document.querySelector("#content-pages > .multipage-slides > .active.show")?.classList.remove("active", "show");
document.querySelector(`#content-pages > .selectors > .nav-item > [data-bs-target="${idAnchor}"]`).classList.add("active");
document.querySelector(`#content-pages > .multipage-slides > ${idAnchor}`).classList.add("active", "show");
};
window.addEventListener("popstate", goTo);
goTo();
require("bootstrap/js/dist/tab.js");
document.querySelector("#content-pages").classList.remove("invisible");
}
async function main() {
initInteractiveElements();
initSlides();
initConfigData();
}
main();