var isProductDetail = false; var products = new Array(); var storeResultsCount = 0; var storeResultsMax = 0; var searchQuery = ""; var categories = new Array(); var alphas = [1.0,0.5,0.25,0.12,0.06,0]; var isTall = false; var currentCategory = "all"; var isDarkMode = false; var stores = new Array(); var locallymadeonly = false; var instockonly = false; var zerowasteonly = false; var chainsAllowed = [1,2,3]; var excludeStores = []; var excludeCats = []; getStores(); mapkit.init({ authorizationCallback: done => { done("eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IkYzWFIyQzk3S0MifQ.eyJpc3MiOiJZRDg4WFpLTUtLIiwiaWF0IjoxNjc1NDYxMTU5LCJleHAiOjE3MDY4MzIwMDAsIm9yaWdpbiI6ImJldGEuanV4dGEuc2hvcCJ9.21tEYSzPPyNqz1p3rcOFG3y92Xg7XxT7nd1-mpKfmLj7sVnqYIgg7JFiUmbzoDBy92dN6fiPkslOzIvQp1b_Pw"); } }); var ogcenter = new mapkit.Coordinate(37.76784189377097, -122.44941338146273), ogspan = new mapkit.CoordinateSpan(0.13, 0.13), ogregion = new mapkit.CoordinateRegion(ogcenter, ogspan); var geocoder = new mapkit.Geocoder({ getsUserLocation: false }); var map = new mapkit.Map("productDetailMapCanvas", { mapType: mapkit.Map.MapTypes.Standard, region: ogregion, showsCompass: mapkit.FeatureVisibility.Hidden, showsZoomControl: true, showsMapTypeControl: false }); function addOrRemove(array,value,shouldAdd) { if(shouldAdd) { if (array.indexOf(value) === -1) { array.push(value); } return array; } else { return array.filter(item => item !== value); } } function filterChecked(obj, checked) { switch (obj.id) { case 'sfmade': locallymadeonly = checked; break; case 'instock': instockonly = checked; break; case 'zerowaste': zerowasteonly = checked; break; case 'regionalchains': chainsAllowed = addOrRemove(chainsAllowed, 1, checked); break; case 'nationalchains': chainsAllowed = addOrRemove(chainsAllowed, 2, checked); break; case 'globalchains': chainsAllowed = addOrRemove(chainsAllowed, 3, checked); break; case 'chains': chainsAllowed = checked ? [1,2,3] : []; break; default: if (obj.id.startsWith("li_store_")) { excludeStores = addOrRemove(excludeStores, obj.id.split("_")[2], !checked); } else if (obj.id.startsWith("li_")) { excludeCats = addOrRemove(excludeCats, obj.id.split("_")[1], !checked); } } updateResults(); } function getStores() { $.getJSON( "src/stores_json.php", function( data ) { var storeQuery = ""; var storeUrlQuery = ""; var matches = searchQuery.match(/(.*)\s+store:\s*(\S+)(\s*.*)/i); if (matches != null && matches.length > 0) { storeQuery = matches[2]; searchQuery = matches[1] + matches[3]; storeUrlQuery = "&store=" + storeQuery; } stores = data["stores"]; storesToFetch = Object.keys(stores).reduce(function (filtered, key) { if (stores[key]["db"] == null || stores[key]["db"] != 1) { if (!(stores[key]["disabled"] != null && stores[key]["disabled"] == "1")) { if ((storeQuery.length > 0 && key == storeQuery) || storeQuery.length <= 0) { filtered[key] = stores[key]; } } } return filtered; }, {}); // storesToFetch = stores.filter(store => store["db"] == null || store["db"] != 1); categories = data["categories"]; storeResultsMax = Object.keys(storesToFetch).length + 1; let selectTag = document.getElementById('search_category'); let filterCatsList = document.getElementById('filter_stores'); var index = 0; for (cat in categories) { index++; let opt = document.createElement("option"); opt.innerHTML = categories[cat]["displayName"]; opt.name = cat; opt.value = cat; opt.selected = (cat == ""); selectTag.append(opt); if(index == 1) { continue; } let filterCat = document.createElement("li"); filterCat.id = "li_" + cat; let filterCatCheck = document.createElement("input"); filterCatCheck.type = "checkbox"; filterCatCheck.value = cat; filterCat.append(filterCatCheck); let filterCatLabel = document.createElement("label"); filterCatLabel.innerHTML = categories[cat]["displayName"]; filterCat.append(filterCatLabel); var matchingStores = storesInCat(stores, cat); if(Object.keys(matchingStores).length > 0) { let filterStores = document.createElement("ul"); for (store in matchingStores) { let filterStore = document.createElement("li"); filterStore.id = "li_store_" + store; let filterStoreCheck = document.createElement("input"); filterStoreCheck.type = "checkbox"; filterStoreCheck.value = matchingStores[store]["name"]; filterStore.append(filterStoreCheck); let filterStoreLabel = document.createElement("label"); filterStoreLabel.innerHTML = matchingStores[store]["name"]; filterStore.append(filterStoreLabel); filterStores.append(filterStore); } filterCat.append(filterStores); } filterCatsList.append(filterCat); } $(function(){ $('ul.tree').checkTree({ onCheck(which) { filterChecked(which[0],true); }, onUnCheck(which) { filterChecked(which[0],false); } }); }); console.log(("plugins/search.php?q=" + encodeURI(searchQuery) + storeUrlQuery)); $.ajax({ url: ("plugins/search.php?q=" + encodeURI(searchQuery) + storeUrlQuery), dataType: "json" }).done(function(stuff) { if(stuff != null) { storeResultsCount++; $("#triangle_fill").width(Math.round((storeResultsCount/storeResultsMax)*100) + "%"); if(storeResultsCount == storeResultsMax) { isLoading = false; } products.push(...stuff); updateResults(); } }); $.each(storesToFetch, function (store, storeData) { // if (storeData["db"] == null || storeData["db"] != 1) { console.log(store); $.ajax({ url: ("plugins/products.php?q=&store=" + store), dataType: "json" }).done(function(stuff) { console.log("got " + store); storeResultsCount++; $("#triangle_fill").width(Math.round((storeResultsCount/storeResultsMax)*100) + "%"); if(storeResultsCount == storeResultsMax) { isLoading = false; } if(stuff != null) { products.push(...stuff); updateResults(); // updateResults(stuff, storeData["categories"][0]); } }); // } }); }); } function hexToRgb(hex) { var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? [ parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16) ] : null; } const pSBC=(p,c0,c1,l)=>{ let r,g,b,P,f,t,h,i=parseInt,m=Math.round,a=typeof(c1)=="string"; if(typeof(p)!="number"||p<-1||p>1||typeof(c0)!="string"||(c0[0]!='r'&&c0[0]!='#')||(c1&&!a))return null; if(!this.pSBCr)this.pSBCr=(d)=>{ let n=d.length,x={}; if(n>9){ [r,g,b,a]=d=d.split(","),n=d.length; if(n<3||n>4)return null; x.r=i(r[3]=="a"?r.slice(5):r.slice(4)),x.g=i(g),x.b=i(b),x.a=a?parseFloat(a):-1 }else{ if(n==8||n==6||n<4)return null; if(n<6)d="#"+d[1]+d[1]+d[2]+d[2]+d[3]+d[3]+(n>4?d[4]+d[4]:""); d=i(d.slice(1),16); if(n==9||n==5)x.r=d>>24&255,x.g=d>>16&255,x.b=d>>8&255,x.a=m((d&255)/0.255)/1000; else x.r=d>>16,x.g=d>>8&255,x.b=d&255,x.a=-1 }return x}; h=c0.length>9,h=a?c1.length>9?true:c1=="c"?!h:false:h,f=this.pSBCr(c0),P=p<0,t=c1&&c1!="c"?this.pSBCr(c1):P?{r:0,g:0,b:0,a:-1}:{r:255,g:255,b:255,a:-1},p=P?p*-1:p,P=1-p; if(!f||!t)return null; if(l)r=m(P*f.r+p*t.r),g=m(P*f.g+p*t.g),b=m(P*f.b+p*t.b); else r=m((P*f.r**2+p*t.r**2)**0.5),g=m((P*f.g**2+p*t.g**2)**0.5),b=m((P*f.b**2+p*t.b**2)**0.5); a=f.a,t=t.a,f=a>=0||t>=0,a=f?a<0?t:t<0?a:a*P+t*p:0; if(h)return"rgb"+(f?"a(":"(")+r+","+g+","+b+(f?","+m(a*1000)/1000:"")+")"; else return"#"+(4294967296+r*16777216+g*65536+b*256+(f?m(a*255):0)).toString(16).slice(1,f?undefined:-2) } // updateResults(); var dm = window.matchMedia('(prefers-color-scheme: dark)'); setColorScheme(dm); dm.addEventListener('change', e => { setColorScheme(e); }); function setColorScheme(e) { isDarkMode = e.matches ? true : false; updateColors(); scrollPage(); } function scrollPage() { var scrollOffset = Math.max(0,window.pageYOffset); var ratio = isTall ? 1.6248 : 0.611111; var spacing = isTall ? 0.15 : 0.2; var posY = isProductDetail ? (scrollOffset % 30) : (scrollOffset % ((window.innerWidth * spacing) * ratio)); var percent = isProductDetail ? (posY / 30) : (posY / ((window.innerWidth * spacing) * ratio)); var expopercent = Math.min(percent * 1.5,1); //Math.pow(percent,0.5); document.getElementById("head").style.transform = "translateY(" + Math.max(0,scrollOffset/2.5) + "px)"; for (var i = 2; i <= 7; i++) { var obj = document.getElementById("triangle" + i); obj.style.transform = "translateY(" + Math.min(0,-posY) + "px)"; // obj.style.top = Math.min(0,-posY) + "px"; obj.style.opacity = ((alphas[Math.max(0,i-3)] - alphas[i-2]) * percent) + alphas[i-2]; if (i == 2) { // var startRGB = hexToRgb(colors[currentCategory]); var startRGB = hexToRgb(primaryColor()); var endRGB = hexToRgb(pSBC((isDarkMode ? 0.15 : -0.3), primaryColor())); // var startRGB = [18,208,184]; // var endRGB = [4, 182, 160]; var rgb = "rgb(" + (((endRGB[0] - startRGB[0]) * expopercent) + startRGB[0]) + "," + (((endRGB[1] - startRGB[1]) * expopercent) + startRGB[1]) + "," + (((endRGB[2] - startRGB[2]) * expopercent) + startRGB[2]) + ")"; obj.style.backgroundColor = rgb; //"rgb(255,50,150)"; //rgb; } } } function primaryColor() { var primary = "#12D0B8"; if (categories != null && Object.keys(categories).length > 1) { primary = categories[currentCategory]["color"]; } return primary; } function updateColors() { map.colorScheme = isDarkMode ? mapkit.Map.ColorSchemes.Dark : mapkit.Map.ColorSchemes.Light; document.documentElement.style.setProperty('--primary', primaryColor()); document.documentElement.style.setProperty('--darker', pSBC((isDarkMode ? 0.15 : -0.3), primaryColor())); var rgb = hexToRgb(pSBC((isDarkMode ? -0.9 : 0.9), primaryColor())); document.documentElement.style.setProperty('--primaryBGTransparent', "rgba(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ",0.5)"); document.documentElement.style.setProperty('--primaryBG', pSBC(0.9, primaryColor())); } function selectedCategory(selectObject) { if(selectObject.value == null || selectObject.value == "") { currentCategory = "all"; } else { currentCategory = selectObject.value; } // currentCategory = selectObject.selectedIndex; document.getElementById("search_field").placeholder = "Search " + currentCategory.toLowerCase() + "..."; updateColors(); clearResults(); updateResults(); scrollPage(); isTall = (currentCategory == "books"); for (var i = 1; i <= 7; i++) { var obj = document.getElementById("triangle" + i); if (isTall) { obj.classList.add("tall"); document.getElementById("main").classList.remove("small"); document.getElementById("main").classList.add("wide"); } else { obj.classList.remove("tall"); document.getElementById("main").classList.remove("wide"); document.getElementById("main").classList.add("small"); } } for (let cat in categories) { if (cat != "all") { if(currentCategory == "all" || cat == currentCategory) { document.getElementById("li_" + cat).classList.remove("hidden"); } else if(!document.getElementById("li_" + cat).classList.contains("hidden")) { document.getElementById("li_" + cat).classList.add("hidden"); } } } } var isLoading = true; var firstRun = true; var count = 2; var productLine; function clearResults() { document.getElementById("main").innerHTML = ''; productLine = null; firstRun = true; count = 2; } function storesInCat(stores, categoryName) { var matchingStores = new Array(); for (store in stores) { let cat = stores[store]["categories"]; if (cat != null && cat.includes(categoryName)) { matchingStores[store] = stores[store]; } } return matchingStores; } function productsInCat(prods, categoryName) { return prods.filter(function(prod) { return prod['categories'].includes(categoryName); }); } function productForIndex(prods, index) { return prods.find(function(prod) { return prod['id'] == index; }); } function matchesQuery(str, query) { if(str == null) { return 0; } str = str.toLowerCase(); query = query.toLowerCase(); var matchCount = query.includes(" ") ? str.includes(query) * 1 : 0; var words = query.split(/\s+/); matchCount += words.map((word,index) => { return hasWord(str,word); }).reduce((a,b) => a+b, 0); return Math.pow(matchCount,2); // Using power to amplify multiple matches } const hasWord = (str, word) => str != null ? str.toLowerCase().split(/\s+/).includes(word.toLowerCase()) : false; function matchScore(prod) { var score = (matchesQuery(prod["title"],searchQuery) * 10) + (matchesQuery(prod["product_type"],searchQuery) * 8) + (matchesQuery(prod["product_tags"],searchQuery) * 4) + (matchesQuery(prod["subtitle"],searchQuery) * 6) + (matchesQuery(prod["author"],searchQuery) * 6) + (matchesQuery(prod["summary"],searchQuery) * 2) + (matchesQuery(prod["description"],searchQuery) * 1); score = score * Math.pow(((4 - stores[prod["store"]]["isChain"]) / 4),0.25); console.log(prod["title"] + ": " + score); return score; } function updateResults(newResults,newCategory) { // var catName = currentCategory; //categories[currentCategory]; // var keys = categories; //Object.keys(products); var results = new Array(); if ((newResults == null || newResults == "") && (searchQuery != null && searchQuery.length > 0)) { clearResults(); results = products; } else { results = newResults; // results[newCategory] = newResults; } var prodsincat = new Array(); if(currentCategory == "all") { prodsincat = results; } else { prodsincat = results.filter(function(prod) { return prod['categories'].includes(currentCategory.toLowerCase()); }); } prodsincat = prodsincat.filter(function(prod) { let store = stores[prod["store"]]; if (store == null) { return false; } if (locallymadeonly == true && (store["sfmade"] == null || store["sfmade"] == false)) { return false; } if (store["isChain"] != null && store["isChain"] != 0) { if (chainsAllowed.indexOf(store["isChain"]) === -1) { return false } } if(excludeStores.includes(prod["store"])) { return false; } if(prod['categories'].some(r => excludeCats.includes(r))) { return false; } return true; }); prodsincat = prodsincat.sort(function(matchA, matchB) { return matchScore(matchB) - matchScore(matchA); }); // for (cat in categories) { // if (cat.toLowerCase() == currentCategory.toLowerCase() || currentCategory == "all") { // var prodsincat = productsInCat(results, cat.toLowerCase()); // if (prodsincat != null && prodsincat.length > 0) { // // // if (results[cat] != null && results[cat].length > 0) { // var index = 0; for (prod of prodsincat) { count++; if (firstRun) { productLine = newObj('div','product_line_init'); firstRun = false; } else if (count % 7 == 0) { document.getElementById("main").appendChild(productLine); productLine = newObj('div','product_line_alt'); } else if (count % 7 == 3) { document.getElementById("main").appendChild(productLine); productLine = newObj('div','product_line'); } var product = newObj('div','product'); if (prod["url"] != null) { product.setAttribute('onclick',"productDetails('" + prod['id'] + "');"); // product.setAttribute('onclick',"productDetails('" + cat + "'," + index + ");"); // product.setAttribute('onclick',"location.href = '" + prod["url"].toString() + "';"); // console.log(prod["url"]); } productLine.appendChild(product); // product.addEventListener("click", function() { // location.href = prod["url"].toString(); // }); var innerProduct = newObj('div','inner_product'); product.appendChild(innerProduct); var productImage = newObj('div','product_image'); productImage.style.backgroundImage = "url('" + prod["imageThumb"] + "')"; innerProduct.appendChild(productImage); var productInfo = newObj('div','product_info'); innerProduct.appendChild(productInfo); var productTitle = newObj('div','product_title'); productTitle.innerHTML = prod["title"]; productInfo.appendChild(productTitle); var productPrice = newObj('div','product_price'); productPrice.innerHTML = prod["price"]; productInfo.appendChild(productPrice); var productLocation = newObj('div','product_location'); // if (prod["url"] != null) { productLocation.innerHTML = "In stock at " + stores[prod["store"]]["name"] + " 4 min"; // } else { // productLocation.innerHTML = "Out of stock"; // } productInfo.appendChild(productLocation); var productAltLocations = newObj('div','product_alt_locations'); productAltLocations.innerHTML = "7 other stores nearby also have this product."; productInfo.appendChild(productAltLocations); // index++; } // } // // } // } if (productLine != null) { document.getElementById("main").appendChild(productLine); } else { if (searchQuery == null || searchQuery.length < 1) { // No search } else { var noResults = newObj('div','results_overlay'); if(isLoading) { noResults.innerHTML = 'Searching for products...'; // noResults.innerHTML = "


Searching for products..."; } else { noResults.innerHTML = "No products matching your search and filters.

Clear Filters"; } document.getElementById("main").appendChild(noResults); } } } function newObj(objType,nameOfClass) { var newDiv = document.createElement(objType); newDiv.className = nameOfClass; return newDiv; } var lastScrollY = 0; function layoutDetail(prod) { document.getElementById('productDetailText').innerHTML = "
" + prod.description; if(prod.images != null && prod.images.length > 0) { if (prod.images[0] != null && prod.images[0].length > 1) { document.getElementById('productDetailImage').style.backgroundImage = "url('" + prod.images[0] + "')"; } var thumbCount = 0; var thumbPos = [ [10.5,8.5], [5.5,-0.25], [0.5,8.5], [5.5,17.25], [10.5,26], [15.25,35], [0.5,26], [5.5,35] ]; for (thumb of prod.images) { if (thumbCount >= thumbPos.length) { break; } if (thumb != null && thumb.length > 1) { var newThumb = newObj('div','productDetailThumb'); newThumb.style.backgroundImage = "url('" + thumb + "')"; newThumb.style.left = thumbPos[thumbCount][0] + "vw"; newThumb.style.top = thumbPos[thumbCount][1] + "vw"; newThumb.setAttribute('onclick',"swapImage(this);"); document.getElementById('productDetailThumbs').appendChild(newThumb); thumbCount++; } } } else { document.getElementById('productDetailImage').style.backgroundImage = "url('" + prod.imageThumb + "')"; } document.getElementById('productDetailInfo_title').innerHTML = prod["title"]; document.getElementById('productDetailInfo_price').innerHTML = prod["price"]; document.getElementById('productDetailInfo_location').innerHTML = "In stock at " + stores[prod["store"]]["name"] + " 4 min"; document.getElementById("productDetail").classList.add("show"); document.getElementById("head").classList.add("productDetailShow"); document.getElementById("main").classList.add("noshow"); document.getElementById("leftPanel").classList.add("hidden"); document.getElementById("logo").classList.add("horizontal"); document.getElementById("logotext").classList.add("horizontal"); for (var i = 1; i <= 7; i++) { var obj = document.getElementById("triangle" + i); obj.classList.add("square"); } window.scrollTo(0,0); } // function productDetails(cat,index) { function productDetails(index) { isProductDetail = !isProductDetail; if (isProductDetail) { lastScrollY = window.pageYOffset; var prod = productForIndex(products,index); geocoder.lookup(stores[prod["store"]]["address"], function(err, data) { var calloutDelegate = { calloutRightAccessoryForAnnotation: function() { var accessoryViewRight = document.createElement("a"); accessoryViewRight.className = "right-accessory-view"; accessoryViewRight.href = "https://www.nps.gov/stli/index.htm"; accessoryViewRight.target = "_blank"; accessoryViewRight.appendChild(document.createTextNode("ⓘ")); return accessoryViewRight; } }; var annotation = new mapkit.MarkerAnnotation(new mapkit.Coordinate(arguments[1].results[0].coordinate.latitude,arguments[1].results[0].coordinate.longitude), { title: stores[prod["store"]]["name"], subtitle: stores[prod["store"]]["address"], callout: calloutDelegate }); // map.center = new mapkit.Coordinate(arguments[1].results[0].coordinate.latitude,arguments[1].results[0].coordinate.longitude); var center = new mapkit.Coordinate(arguments[1].results[0].coordinate.latitude,arguments[1].results[0].coordinate.longitude), span = new mapkit.CoordinateSpan(0.02, 0.02), region = new mapkit.CoordinateRegion(center, span); map.setRegionAnimated(region); map.addAnnotation(annotation); }); // if not db if(stores[prod["store"]]["db"] == null || stores[prod["store"]]["db"] != 1) { document.getElementById('productDetailText').innerHTML = '
Loading product details...
'; // document.getElementById('productDetailText').innerHTML = "

Loading product details...
" $.post( "plugins/product.php", { "product": prod }, function( prodDetails ) { clearThumbs(); prod["images"] = prodDetails["images"]; prod["description"] = prodDetails["description"]; layoutDetail(prod); //Object.assign({}, prod, prodDetails)); }, "json"); } else { // else db clearThumbs(); layoutDetail(prod); //{"description": prod["description"],"images": prod["images"]}); } // document.getElementById('productDetailImage').style.backgroundImage = "url('" + prod["image"][0] + "')"; } else { map.removeAnnotations(map.annotations); map.region = ogregion; document.getElementById("leftPanel").classList.remove("hidden"); document.getElementById("logo").classList.remove("horizontal"); document.getElementById("logotext").classList.remove("horizontal"); document.getElementById("main").classList.remove("noshow"); document.getElementById("productDetail").classList.remove("show"); document.getElementById("head").classList.remove("productDetailShow"); for (var i = 1; i <= 7; i++) { var obj = document.getElementById("triangle" + i); obj.classList.remove("square"); } window.scrollTo(0,lastScrollY); clearThumbs(); } } function swapImage(el) { document.getElementById("productDetailImage").style.backgroundImage = el.style.backgroundImage; } function clearThumbs() { document.getElementById("productDetailThumbs").innerHTML = ''; }