300 lines
15 KiB
JavaScript
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();
|