Bokeh Tap tool as trigger callback for different figure - javascript

I am trying to use the taptool indices to trigger a callback to another plot/figure.
I adapted my data to fit few examples I have found online as I suck in JS.
I have been stuck for a good while, so any help would be very helpful. I tried to simply the code and unfortunately I can not share the data, but I believe that the code below it is very straightforward.
I am getting a warning that a warning for this line of code below:
wP.select(tap).callback = CustomJS(args = {'week': wP, 'yCol': yCol}, code = code)
'generator' object has no attribute 'callback'
df = YearlyDF_var('Precipitation', 'median')
y = 2020
p1 = figure(match_aspect=True, x_axis_location = None, y_axis_location = None, title='Kalimantan Central',
tools = 'pan, wheel_zoom, box_zoom, reset, save, tap', title_location='below', toolbar_location='above',
plot_height=550)
src = ColumnDataSource(data=df)
for1 = PrintfTickFormatter(format='%0.0f mm')
p1.image_url(url=['BaritoLine3.png'], x=114.368, y=-1.97, w=1.1, h=3.04, anchor="center")
t2 = p1.circle('x', 'y', line_color='black', size=30, alpha=.7, line_width=.5, color='c', muted_alpha=0.3, source=src)
p1.add_layout(t2)
hvF = HoverTool()
hvF.tooltips=[('Collection', '#Location'), ('', '#2020{0.00 a} mm')]
p1.add_tools(hvF)
p1.grid.grid_line_color=None
p1.outline_line_color=None
p1.title.text_font_style = 'bold'
p1.title.text_font_size = '25px'
p1.title.text_color = Viridis[11][3]
p1.title.align = 'center'
p1.toolbar.autohide = True
df1 = pd.DataFrame()
df1 = dfW1.iloc[dfW1.index.get_level_values('yearRaw') == 2020][['Precipitation']]
#dfY = df[[str(var)]]
df2 = pd.DataFrame()
df2[['week', 'Muara_Teweh', 'Muara_Tuhup', 'Puruk_Cahu', 'Taboneo_Anchorage', 'Tarusan', 'Teluk_Siwak', 'Ujung_Rumput']] = None
df2['week'] = sorted(set(df1.index.get_level_values('weekRaw').map(lambda x: x.split(' ')[0])))
df2.set_index('week', inplace=True)
df2['Muara_Teweh'] = df1.query("Location == 'Muara Teweh'").values
df2['Muara_Tuhup'] = df1.query("Location == 'Muara Tuhup'").values
df2['Puruk_Cahu'] = df1.query("Location == 'Puruk Cahu'").values
df2['Taboneo_Anchorage'] = df1.query("Location == 'Taboneo Anchorage'").values
df2['Tarusan'] = df1.query("Location == 'Tarusan'").values
df2['Teluk_Siwak'] = df1.query("Location == 'Teluk Siwak'").values
df2['Ujung_Rumput'] = df1.query("Location == 'Ujung Rumput'").values
yCol = [df2['Muara_Teweh'].to_numpy(),
df2['Muara_Tuhup'].to_numpy(),
df2['Puruk_Cahu'].to_numpy(),
df2['Taboneo_Anchorage'].to_numpy(),
df2['Tarusan'].to_numpy(),
df2['Teluk_Siwak'].to_numpy(),
df2['Ujung_Rumput'].to_numpy()]
pWeekly = figure(x_range=df2.index.to_list(), plot_height=300, plot_width=700,
title='Weekly test', tools='pan, wheel_zoom, box_zoom, reset', toolbar_location='right')
wP = pWeekly.vbar(x='x', top='y', source=ColumnDataSource({'x': df2.index.to_list(), 'y':yCol[0]}), width=0.5, color=Paired[7][0])
pWeekly.toolbar.autohide = True
pWeekly.grid.grid_line_dash = 'dotted'
pWeekly.grid.grid_line_dash_offset = 5
pWeekly.grid.grid_line_width = 2
pWeekly.outline_line_color=None
pWeekly.axis.major_label_text_font_style = 'bold'
pWeekly.axis.major_label_text_font_size = '11px'
pWeekly.axis.axis_label_text_font_style = 'bold'
pWeekly.xaxis.major_label_orientation = 45
pWeekly.yaxis[0].formatter = PrintfTickFormatter(format='%0.0f mm')
pWeekly.y_range.start = round(df2.min().min(), -3)
code = '''if (cb_data.source.selected.indices.length == 1){
var selected_index = cb_data.source.selected.indices[0];
week.data_source.data['y'] = yCol[selected_index]
week.data_source.change.emit();
}'''
tap = TapTool(renderers=[t2], behavior='select')
wP.select(tap).callback = CustomJS(args = {'week': wP, 'yCol': yCol}, code = code)
show(column([p1, pWeekly]))

Related

Async/await function to show, wait, hide overlay not working in javascript

I'm having a problem with showing an overlay div element and then hiding it again after the runSearch() function has completed. In short, the overlay does not appear at all.
What the overlay should look like if it worked:
If I had to guess, I believe it could relate to a misunderstanding about how to implement async/await correctly in javascript.
Since I am limited for space here, the full Github project is accessible as a fully deployed page here, if you need more context. However, the most relevant excerpts are below:
The overlay div element in index.html:
<div class="overlay d-flex justify-content-center align-items-center">
<h5>Please wait...</h5>
<div class="spinner-border" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
The overlay in CSS:
.overlay {
background-color:#EFEFEF;
position: fixed;
width: 100%;
height: 100%;
z-index: 1000;
left: 0px;
display: none!important;
/* without !important, the overlay would immediately kick into effect */
}
The JS functions which show and hide the overlay when called upon:
function loadingOverlayOn() {
document
.getElementsByClassName("overlay")[0]
.style.display = 'block'
}
function loadingOverlayOff() {
document
.getElementsByClassName("overlay")[0]
.style.display = 'none'
}
JS with respect to button #1:
cityInstanceBtn.addEventListener('click',async function(e){
// for use in headings inside runSearch
// reset
globalCityName === null;
globalCityState === null;
globalCityCountry === null;
globalCityName = e.target.dataset.city
globalCityState = e.target.dataset.state
globalCityCountry = e.target.dataset.country
loadingOverlayOn();
await runSearch(cityName, cityState, cityCountry, cityLat, cityLng, units)
loadingOverlayOff();
})
JS with respect to button #2, which occurs inside of a temporarily displayed Bootstrap modal:
cityInstanceBtn.addEventListener('click', async function(){
myModal.hide()
globalCityName = document.getElementById(id).dataset.city
globalCityState = document.getElementById(id).dataset.state
globalCityCountry = document.getElementById(id).dataset.country
loadingOverlayOn();
await runSearch(cityName, cityState, cityCountry, cityLat, cityLng, units)
loadingOverlayOff();
})
The JS function during which the overlay should be shown, and hidden once its execution is complete:
async function runSearch(
cityName,
cityState,
country,
cityLat,
cityLng,
detectedUnits
) {
console.log("check cityState: " + cityState);
console.log("check globalCityState: " + globalCityState);
var h2Today = document.getElementById("today-title");
var h2Next5Days = document.getElementById("next-5-days-title");
if (globalCityState != "undefined" && globalCityName && globalCityCountry) {
h2Today.innerHTML = `<span class="orange">Today's</span> forecast for <span class="cornflowerblue">${globalCityName}, ${globalCityState}, ${globalCityCountry}</span>`;
h2Next5Days.innerHTML = `<span class="orange">4-day</span> outlook for <span class="cornflowerblue">${globalCityName}, ${globalCityState}, ${globalCityCountry}</span>`;
} else if (
(globalCityState = "undefined" && globalCityName && globalCityCountry)
) {
h2Today.innerHTML = `<span class="orange">Today's</span> forecast for <span class="cornflowerblue">${globalCityName},${globalCityCountry}</span>`;
h2Next5Days.innerHTML = `<span class="orange">4-day</span> outlook for <span class="cornflowerblue">${globalCityName}, ${globalCityCountry}</span>`;
}
var newSearchObject = {
cityName: cityName,
cityState: cityState,
cityCountry: country,
cityLat: cityLat,
cityLng: cityLng,
detectedUnits: detectedUnits,
};
var retrievedLocalStorage = localStorage.getItem("savedCities");
retrievedLocalStorage = JSON.parse(retrievedLocalStorage);
// const arr = retrievedLocalStorage.map(a => {a.cityLat, a.cityLng})
if (retrievedLocalStorage === null) {
localStorage.setItem("savedCities", JSON.stringify([newSearchObject]));
generatePrevCitiesList();
} else if (
retrievedLocalStorage.length > 0 &&
retrievedLocalStorage.length < 5
) {
retrievedLocalStorage.reverse();
if (
!retrievedLocalStorage.some((s) => {
return (
s.cityLat == newSearchObject.cityLat &&
s.cityLng == newSearchObject.cityLng
);
})
) {
// Check if an array of objects contains another object: https://stackoverflow.com/a/63336477/9095603
// this solution which converts objects to string first isn't entirely reliable if you can't guarantee the same order is preserved, for example: https://stackoverflow.com/a/201305/9095603
retrievedLocalStorage.push(newSearchObject);
retrievedLocalStorage.reverse();
console.log("existingSearchObject2: " + retrievedLocalStorage);
localStorage.setItem(
"savedCities",
JSON.stringify(retrievedLocalStorage)
);
}
generatePrevCitiesList();
} else if (retrievedLocalStorage.length >= 5) {
retrievedLocalStorage.reverse();
if (
!retrievedLocalStorage.some((s) => {
return (
s.cityLat == newSearchObject.cityLat &&
s.cityLng == newSearchObject.cityLng
);
})
) {
retrievedLocalStorage.push(newSearchObject);
}
while (retrievedLocalStorage.length > 5) {
retrievedLocalStorage.shift();
}
retrievedLocalStorage.reverse();
localStorage.setItem("savedCities", JSON.stringify(retrievedLocalStorage));
generatePrevCitiesList();
}
fetch(
`https://api.openweathermap.org/data/2.5/forecast?lat=${cityLat}&lon=${cityLng}&units=${detectedUnits}&appid=${apiKey}`
)
.then((response) => response.json())
.then((data) => {
console.log(data);
console.table(data.list);
console.log(JSON.stringify(data));
var timezone = data.city.timezone;
console.log({ timezone });
var country = data.city.country;
console.log({ country });
var cityName = data.city.name;
console.log({ cityName });
var datesArray = [];
console.log({ datesArray });
const days = [
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
];
// var h2Today = document.getElementById('today-title')
// h2Today.innerHTML = `<span class="orange">Today's</span> forecast for <span class="cornflowerblue">${globalCityName}, ${globalCityState}, ${globalCityCountry}</span>`
// }
// h2Today.innerHTML = `<span class="orange">Today's</span> forecast for <span class="cornflowerblue">${globalCityName},${globalCityCountry}</span>`
// }
for (let i = 0; i < data.list.length; i++) {
var unixTimestamp = data.list[i].dt;
console.log(data.list[i].dt);
// you don't need it for dt_txt but if you want to use the unix timestamp in the data, you can do this conversion:
var jsTimestamp = unixTimestamp * 1000;
var date = new Date(jsTimestamp);
var basicDateLocalAU = date.toLocaleDateString("en-AU");
var basicDateLocalUS = date.toLocaleDateString("en-US");
var basicDateLocalUser = date.toLocaleDateString(`en-${country}`);
console.log(basicDateLocalAU); // Prints: 5/6/2022
console.log(basicDateLocalUS); // Prints: 6/5/2022
console.log(basicDateLocalUser); // Prints: 6/5/2022
var timeLocalAU = date.toLocaleTimeString("en-AU", {
hour: "2-digit",
minute: "2-digit",
}); // Prints: 13:10:34
// https://stackoverflow.com/a/20430558/9095603
// https://bobbyhadz.com/blog/javascript-typeerror-date-getday-is-not-a-function#:~:text=getDay%20is%20not%20a%20function%22%20error%20occurs%20when%20the%20getDay,method%20on%20valid%20date%20objects.
data.list[i].basicDateLocalAU = basicDateLocalAU;
data.list[i].basicDateLocalUS = basicDateLocalUS;
data.list[i].basicDateLocalUser = basicDateLocalUser;
data.list[i].dayOfWeekIndex = date.getDay();
data.list[i].dayOfWeekValue = days[date.getDay()];
data.list[i].basicTime = timeLocalAU;
// https://bobbyhadz.com/blog/javascript-array-push-if-not-exist
if (!datesArray.includes(basicDateLocalUser)) {
datesArray.push(basicDateLocalUser);
var dayOfWeek = days[date.getDay()];
console.log(dayOfWeek);
}
}
console.log({ date });
console.log({ data });
var datalist = data.list;
console.log({ datalist });
var obj = groupBy(datalist, "basicDateLocalAU");
console.log({ obj });
// const result = data.list.group(({ basicCalendarDateAU }) => basicCalendarDateAU);
for (let i = 0; i < obj.length; i++) {
var dayTableEle = document.querySelector(`#day${i} table`);
// var textNode = document.createTextNode(`${dayOfWeekValue}`);
dayTableEle.innerHTML = `<row><th>Time</th><th>Temp</th><th></th><th>Conditions</th><th>Humidity</th><th>Wind speed</th></row>`;
for (let j = 0; j < obj[i].length; j++) {
console.log(obj[i].length);
if (!document.querySelector(`#day${i} h5`).innerText) {
document.querySelector(
`#day${i} h5`
).innerText = `${obj[i][j].dayOfWeekValue}`;
}
if (
!document.querySelector(`#day${i} span#usercountry-dateformat`)
.innerText
) {
document.querySelector(
`#day${i} span#usercountry-dateformat`
).innerText = `${obj[i][j].basicDateLocalUser}`;
}
if (
!document.querySelector(`#day${i} span#AU-dateformat`).innerText
) {
document.querySelector(
`#day${i} span#AU-dateformat`
).innerText = `${obj[i][j].basicDateLocalAU}`;
document
.querySelector(`#day${i} span#AU-dateformat`)
.style.setProperty("display", "none");
}
if (
!document.querySelector(`#day${i} span#US-dateformat`).innerText
) {
document.querySelector(
`#day${i} span#US-dateformat`
).innerText = `${obj[i][j].basicDateLocalUS}`;
document
.querySelector(`#day${i} span#US-dateformat`)
.style.setProperty("display", "none");
}
// var kelvinToCelcius = obj[i][j].main.temp - 273.15;
var tempMetric;
var tempImperial;
var windSpeedImperial;
var windSpeedMetric;
if (units == "metric") {
var tempMetric = obj[i][j].main.temp;
tempMetric = roundedToFixed(tempMetric, 1);
var tempImperial = tempMetric * 1.8 + 32;
tempImperial = roundedToFixed(tempImperial, 1);
var windSpeedMetric = obj[i][j].wind.speed;
windSpeedMetric = roundedToFixed(windSpeedMetric, 1);
var windSpeedImperial = windSpeedMetric * 2.23694;
windSpeedImperial = roundedToFixed(windSpeedImperial, 1);
var metricDisplay = "inline";
var imperialDisplay = "none";
} else if (units == "imperial") {
var tempImperial = obj[i][j].main.temp;
tempImperial = roundedToFixed(tempImperial, 1);
var tempMetric = (tempImperial - 32) / 1.8;
tempMetric = roundedToFixed(tempMetric, 1);
var windSpeedImperial = obj[i][j].wind.speed;
windSpeedImperial = roundedToFixed(windSpeedImperial, 1);
var windSpeedMetric = windSpeedImperial / 2.23694;
windSpeedMetric = roundedToFixed(windSpeedMetric, 1);
var metricDisplay = "none";
var imperialDisplay = "inline";
}
dayTableEle.innerHTML += `
<row>
<td id="tdTime">${obj[i][j].basicTime}</td>
<td id="tdTemp">
<span class="temp-metric metric" style="display:${metricDisplay};">${tempMetric} ${tempUnitsMetric}</span>
<span class="temp-imperial imperial" style="display:${imperialDisplay};">${tempImperial} ${tempUnitsImperial}</span>
</td>
<td><img src="https://openweathermap.org/img/wn/${obj[i][j].weather[0].icon}.png" alt="weather icon"></td>
<td id="tdConditions">${obj[i][j].weather[0].description}</td>
<td id="tdHumidity">${obj[i][j].main.humidity} %</td>
<td id="tdWindSpeed">
<span class="windspeed-metric metric" style="display:${metricDisplay};">${windSpeedMetric} ${windSpeedUnitsMetric}</span>
<span class="windspeed-imperial imperial" style="display:${imperialDisplay};">${windSpeedImperial} ${windSpeedUnitsImperial}</span>
</td>
<td id="tdWindDir"><i style="transform: rotate(${obj[i][j].wind.deg}deg)" class="fa-solid fa-arrow-up"></i></td>
</row>
`;
}
}
});
}
We can see here that the event listener is properly attached - this is true of both buttons but I'll show one here just to be representative:
Full Github project is accessible as a fully deployed page here.
To reiterate, the problem is that the overlay does not appear at all during this sequence of events and I'm seeing page elements prematurely before the page is built:
loadingOverlayOn();
await runSearch(cityName, cityState, cityCountry, cityLat, cityLng, units)
loadingOverlayOff();
You are not awaiting fetch, you are using then instead.
You have to await fetch
See below example
const response = await fetch(url);
const jsonData = await response.json()

Interact between values

I'm wondering how to stop counting money (iloscPieniedzy) when HP (iloscZycia) is 0 in this code:
let iloscZycia = 20;
const sumaZycia = document.getElementById("suma-zycia");
const dodajZycie = document.getElementById('dodaj-zycie');
const odejmijZycie = document.getElementById('odejmij-zycie');
dodajZycie.addEventListener("click", function() {
iloscZycia++;
sumaZycia.textContent = iloscZycia;
});
odejmijZycie.addEventListener("click", function() {
iloscZycia = iloscZycia - 5;
if (iloscZycia <= 0) {
iloscZycia = 0
};
sumaZycia.textContent = iloscZycia;
});
let iloscPieniedzy = 0;
const sumaPieniedzy = document.getElementById("suma-pieniedzy");
const dodajPieniadze = document.getElementById('dodaj-pieniadze');
const odejmijPieniadze = document.getElementById('odejmij-pieniadze');
dodajPieniadze.addEventListener("click", function() {
iloscPieniedzy = iloscPieniedzy + 10;
sumaPieniedzy.textContent = iloscPieniedzy;
});
odejmijPieniadze.addEventListener("click", function() {
iloscPieniedzy = iloscPieniedzy - 1;
if (iloscPieniedzy <= 0) {
iloscPieniedzy = 0
};
sumaPieniedzy.textContent = iloscPieniedzy;
});
I tried something like this:
if (sumaZycia=0){
sumaPieniedzy=0
};
but even this doesn't work like it's not connected.
the = operator is used to assign values to variables what you're looking for is the equality operator ===.
so try it like this:
if (iloscZycia === 0){
iloscPieniedzy = 0
};
Because Stack Overflow is an English language site, I've renamed your variables and created a map of the new names to previous names in comments at the top of the JavaScript below.
If you create a functional closure for updating your numeric variables, you can encapsulate the necessary logic for only updating money when hp is not equal to 0.
Here's a working example in a code snippet:
// hp: iloscZycia
// element1: sumaZycia
// element2: dodajZycie
// element3: odejmijZycie
// money: iloscPieniedzy
// element4: sumaPieniedzy
// element5: dodajPieniadze
// element6: odejmijPieniadze
let hp = 20;
const adjustHp = (amount) => {
hp += amount;
if (hp < 0) hp = 0;
element1.textContent = hp;
};
const element1 = document.getElementById("suma-zycia");
const element2 = document.getElementById("dodaj-zycie");
const element3 = document.getElementById("odejmij-zycie");
element1.textContent = hp;
element2.addEventListener("click", () => adjustHp(1));
element3.addEventListener("click", () => adjustHp(-5));
let money = 0;
const adjustMoney = (amount) => {
// Update money, but ONLY if hp is not 0:
if (hp !== 0) {
money += amount;
if (money < 0) money = 0;
}
element4.textContent = money;
};
const element4 = document.getElementById("suma-pieniedzy");
const element5 = document.getElementById("dodaj-pieniadze");
const element6 = document.getElementById("odejmij-pieniadze");
element4.textContent = money;
element5.addEventListener("click", () => adjustMoney(10));
element6.addEventListener("click", () => adjustMoney(-1));
.group { display: flex; gap: 0.5rem; }
<h2>HP</h2>
<div class="group">
<button id="odejmij-zycie">-</button>
<div id="suma-zycia">0</div>
<button id="dodaj-zycie">+</button>
</div>
<h2>Money</h2>
<div class="group">
<button id="odejmij-pieniadze">-</button>
<div id="suma-pieniedzy">0</div>
<button id="dodaj-pieniadze">+</button>
</div>

How can I decrease loading time of scripts in vue.js?

Issues
I am building a card builder app and the builder is split into three sections; the create (user enters details), the builder (using vue.draggable user can drag stamps on to card) and the preview (card is displayed to user).
I use jQuery to fade each section in and out and I want to increase the speed of the loading time - my issues is that when the build section is first loaded in, it takes a long time, but then it works as expected.
Failed Solutions
I first thought that is was the png stamp images that were increasing the loading time and so I uploaded them to compress.io and this decreased the image sizes from 406KB to 82KB
I then research some solutions and tried compressing the jQuery the original file looks like this:
$(document).ready(function () {
let btn1 = $('#btn-1')
let btn2 = $('#btn-2')
let btn3 = $('#btn-3')
let btn1Txt = $('#btn-1-txt')
let btn2Txt = $('#btn-2-txt')
let btn3Txt = $('#btn-3-txt')
let create = $('.create-container')
let build = $('.build-container')
let preview = $('.preview-container')
let cardTitle
let cardDescription
let logoAlign
let logoShape
let cardSlots
let cardColour
btn1.addClass('active-btn')
btn1Txt.addClass('active-txt')
create.fadeIn(250)
btn1.click(function () {
btn1.addClass('active-btn')
btn1Txt.addClass('active-txt')
btn2.removeClass('active-btn')
btn2Txt.removeClass('active-txt')
btn3.removeClass('active-btn')
btn3Txt.removeClass('active-txt')
build.fadeOut(100)
preview.fadeOut(100)
create.delay(250).fadeIn(100)
});
btn2.click(function () {
btn2.addClass('active-btn')
btn2Txt.addClass('active-txt')
btn1.removeClass('active-btn')
btn1Txt.removeClass('active-txt')
btn3.removeClass('active-btn')
btn3Txt.removeClass('active-txt')
create.fadeOut(100)
preview.fadeOut(100)
build.delay(250).fadeIn(100)
});
btn3.click(function () {
btn3.addClass('active-btn')
btn3Txt.addClass('active-txt')
btn1.removeClass('active-btn')
btn1Txt.removeClass('active-txt')
btn2.removeClass('active-btn')
btn2Txt.removeClass('active-txt')
create.fadeOut(100)
build.fadeOut(100)
preview.delay(250).fadeIn(100)
});
let titleHelper = $('#title-helper')
let titleHelpShow = $('#title-helper-show')
let titleHelpHide = $('#title-helper-close')
let titleInput = $('#title-input')
let descHelper = $('#desc-helper')
let descHelpShow = $('#desc-helper-show')
let descHelpHide = $('#desc-helper-close')
let descInput = $('#desc-input')
let logoHelper = $('#logo-helper')
let logoHelpShow = $('#logo-helper-show')
let logoHelpHide = $('#logo-helper-close')
let logoShapeHelper = $('#logo-shape-helper')
let logoShapeHelpShow = $('#logo-shape-helper-show')
let logoShapeHelpHide = $('#logo-shape-helper-close')
let colourHelper = $('#colour-helper')
let colourHelpShow = $('#colour-helper-show')
let colourHelpHide = $('#colour-helper-close')
let colourInput = $('#colour-input')
let numHelper = $('#num-helper')
let numHelpShow = $('#num-helper-show')
let numHelpHide = $('#num-helper-close')
let numInput = $('#num-input')
let alignLeft = $('#align-left-opt')
let alignCenter = $('#align-center-opt')
let alignRight = $('#align-right-opt')
let circleLogo = $('#circle-logo')
let squareLogo = $('#square-logo')
let stamp5 = $('#stamps-5')
let stamp10 = $('#stamps-10')
let stamp15 = $('#stamps-15')
let colourPicker = $('#colour-input')
titleHelpShow.click(function() {titleHelper.fadeIn(200)});
titleHelpHide.click(function() {titleHelper.fadeOut(200)});
titleInput.click(function() {titleHelper.fadeOut(200)});
descHelpShow.click(function() {descHelper.fadeIn(200)});
descHelpHide.click(function() {descHelper.fadeOut(200)});
descInput.click(function() {descHelper.fadeOut(200)});
logoHelpShow.click(function() {logoHelper.fadeIn(200)});
logoHelpHide.click(function() {logoHelper.fadeOut(200)});
alignLeft.click(function() {logoHelper.fadeOut(200)});
alignCenter.click(function() {logoHelper.fadeOut(200)});
alignRight.click(function() {logoHelper.fadeOut(200)});
logoShapeHelpShow.click(function() {logoShapeHelper.fadeIn(200)});
logoShapeHelpHide.click(function() {logoShapeHelper.fadeOut(200)});
circleLogo.click(function() {logoShapeHelper.fadeOut(200)});
squareLogo.click(function() {logoShapeHelper.fadeOut(200)});
numHelpShow.click(function() {numHelper.fadeIn(200)});
numHelpHide.click(function() {numHelper.fadeOut(200)});
numInput.click(function() {numHelper.fadeOut(200)});
stamp5.click(function() {numHelper.fadeOut(200)});
stamp10.click(function() {numHelper.fadeOut(200)});
stamp15.click(function() {numHelper.fadeOut(200)});
colourHelpShow.click(function() {colourHelper.fadeIn(200)});
colourHelpHide.click(function() {colourHelper.fadeOut(200)});
colourInput.click(function() {colourHelper.fadeOut(200)});
alignLeft.click(function() {
alignRight.removeClass('active-btn')
alignCenter.removeClass('active-btn')
alignLeft.addClass('active-btn')
});
alignCenter.click(function () {
alignRight.removeClass('active-btn')
alignLeft.removeClass('active-btn')
alignCenter.addClass('active-btn')
});
alignRight.click(function () {
alignLeft.removeClass('active-btn')
alignCenter.removeClass('active-btn')
alignRight.addClass('active-btn')
});
circleLogo.click(function () {
squareLogo.removeClass('active-btn')
circleLogo.addClass('active-btn')
});
squareLogo.click(function () {
circleLogo.removeClass('active-btn')
squareLogo.addClass('active-btn')
});
stamp5.click(function () {
stamp10.removeClass('active-btn')
stamp15.removeClass('active-btn')
stamp5.addClass('active-btn')
});
stamp10.click(function () {
stamp5.removeClass('active-btn')
stamp15.removeClass('active-btn')
stamp10.addClass('active-btn')
});
stamp15.click(function () {
stamp10.removeClass('active-btn')
stamp5.removeClass('active-btn')
stamp15.addClass('active-btn')
});
cardTitle = "Untitled Card"
cardDescription = "This is the card Description"
logoAlign = "Left"
logoShape = "Square"
cardSlots = 5
cardColour = colourPicker.val()
titleInput.change(function() {cardTitle = titleInput.val()});
descInput.change(function() {cardDescription = descInput.val()});
alignLeft.click(function() {logoAlign = "Left"});
alignCenter.click(function() {logoAlign = "Center"});
alignRight.click(function() {logoAlign = "Right"});
circleLogo.click(function() {logoShape = "Circle"});
squareLogo.click(function() {logoShape = "Square"});
stamp5.click(function () {cardSlots = 5});
stamp10.click(function () {cardSlots = 10});
stamp15.click(function () {cardSlots = 15});
colourPicker.change(function() {cardColour = colourPicker.val()});
let logo = $('.sample-logo-container')
let logoPosition = $('.logo-align-container')
let cardHolder = $('.builder-card')
let stampRow1 = $('#stamp-row-1')
let stampRow2 = $('#stamp-row-2')
let stampRow3 = $('#stamp-row-3')
btn2.click(function () {
console.log("Title: " + cardTitle)
console.log("Description: " + cardDescription)
console.log("Logo Alignment: " + logoAlign)
console.log("Logo Shape: " + logoShape)
console.log("Num of stamps: " + cardSlots)
console.log("Background Colour: " + cardColour)
if (logoShape === "Square") {
logo.removeClass('circle-logo')
logo.addClass('square-logo')
}
if (logoShape === "Circle") {
logo.removeClass('square-logo')
logo.addClass('circle-logo')
}
if (logoAlign === "Left") {
logoPosition.removeClass('logo-align-middle')
logoPosition.removeClass('logo-align-right')
logo.removeClass('logo-align-middle')
logo.removeClass('m-inline-e')
logoPosition.addClass('logo-align-left')
logo.addClass('m-inline-s')
}
if (logoAlign === "Center") {
logoPosition.removeClass('logo-align-left')
logoPosition.removeClass('logo-align-right')
logo.removeClass('m-inline-s')
logo.removeClass('m-inline-e')
logoPosition.addClass('logo-align-middle')
logo.addClass('logo-align-middle')
}
if (logoAlign === "Right") {
logoPosition.removeClass('logo-align-left')
logoPosition.removeClass('logo-align-middle')
logo.removeClass('m-inline-s')
logo.removeClass('logo-align-middle')
logoPosition.addClass('logo-align-right')
logo.addClass('m-inline-e')
}
if (cardSlots === 5) {
stampRow1.show()
stampRow2.hide()
stampRow3.hide()
cardHolder.removeClass('two-stamp-row-height')
cardHolder.removeClass('three-stamp-row-height')
cardHolder.addClass('one-stamp-row-height')
}
if (cardSlots === 10) {
stampRow1.show()
stampRow2.show()
stampRow3.hide()
cardHolder.removeClass('one-stamp-row-height')
cardHolder.removeClass('three-stamp-row-height')
cardHolder.addClass('two-stamp-row-height')
}
if (cardSlots === 15) {
stampRow1.show()
stampRow2.show()
stampRow3.show()
cardHolder.removeClass('one-stamp-row-height')
cardHolder.removeClass('two-stamp-row-height')
cardHolder.addClass('three-stamp-row-height')
}
cardHolder.css("background-color", cardColour)
});
});
And the compressed file looks like this:
$(document).ready(function(){let e=$("#btn-1"),t=$("#btn-2"),l=$("#btn-3"),a=$("#btn-1-txt"),o=$("#btn-2-txt"),c=$("#btn-3-txt"),i=$(".create-container"),n=$(".build-container"),s=$(".preview-container"),d,r,u,v,f,m;e.addClass("active-btn"),a.addClass("active-txt"),i.fadeIn(250),e.click(function(){e.addClass("active-btn"),a.addClass("active-txt"),t.removeClass("active-btn"),o.removeClass("active-txt"),l.removeClass("active-btn"),c.removeClass("active-txt"),n.fadeOut(100),s.fadeOut(100),i.delay(250).fadeIn(100)}),t.click(function(){t.addClass("active-btn"),o.addClass("active-txt"),e.removeClass("active-btn"),a.removeClass("active-txt"),l.removeClass("active-btn"),c.removeClass("active-txt"),i.fadeOut(100),s.fadeOut(100),n.delay(250).fadeIn(100)}),l.click(function(){l.addClass("active-btn"),c.addClass("active-txt"),e.removeClass("active-btn"),a.removeClass("active-txt"),t.removeClass("active-btn"),o.removeClass("active-txt"),i.fadeOut(100),n.fadeOut(100),s.delay(250).fadeIn(100)});let C=$("#title-helper"),g=$("#title-helper-show"),h=$("#title-helper-close"),p=$("#title-input"),k=$("#desc-helper"),b=$("#desc-helper-show"),w=$("#desc-helper-close"),O=$("#desc-input"),x=$("#logo-helper"),I=$("#logo-helper-show"),q=$("#logo-helper-close"),L=$("#logo-shape-helper"),y=$("#logo-shape-helper-show"),S=$("#logo-shape-helper-close"),D=$("#colour-helper"),R=$("#colour-helper-show"),T=$("#colour-helper-close"),A=$("#colour-input"),B=$("#num-helper"),N=$("#num-helper-show"),U=$("#num-helper-close"),j=$("#num-input"),z=$("#align-left-opt"),E=$("#align-center-opt"),F=$("#align-right-opt"),G=$("#circle-logo"),H=$("#square-logo"),J=$("#stamps-5"),K=$("#stamps-10"),M=$("#stamps-15"),P=$("#colour-input");g.click(function(){C.fadeIn(200)}),h.click(function(){C.fadeOut(200)}),p.click(function(){C.fadeOut(200)}),b.click(function(){k.fadeIn(200)}),w.click(function(){k.fadeOut(200)}),O.click(function(){k.fadeOut(200)}),I.click(function(){x.fadeIn(200)}),q.click(function(){x.fadeOut(200)}),z.click(function(){x.fadeOut(200)}),E.click(function(){x.fadeOut(200)}),F.click(function(){x.fadeOut(200)}),y.click(function(){L.fadeIn(200)}),S.click(function(){L.fadeOut(200)}),G.click(function(){L.fadeOut(200)}),H.click(function(){L.fadeOut(200)}),N.click(function(){B.fadeIn(200)}),U.click(function(){B.fadeOut(200)}),j.click(function(){B.fadeOut(200)}),J.click(function(){B.fadeOut(200)}),K.click(function(){B.fadeOut(200)}),M.click(function(){B.fadeOut(200)}),R.click(function(){D.fadeIn(200)}),T.click(function(){D.fadeOut(200)}),A.click(function(){D.fadeOut(200)}),z.click(function(){F.removeClass("active-btn"),E.removeClass("active-btn"),z.addClass("active-btn")}),E.click(function(){F.removeClass("active-btn"),z.removeClass("active-btn"),E.addClass("active-btn")}),F.click(function(){z.removeClass("active-btn"),E.removeClass("active-btn"),F.addClass("active-btn")}),G.click(function(){H.removeClass("active-btn"),G.addClass("active-btn")}),H.click(function(){G.removeClass("active-btn"),H.addClass("active-btn")}),J.click(function(){K.removeClass("active-btn"),M.removeClass("active-btn"),J.addClass("active-btn")}),K.click(function(){J.removeClass("active-btn"),M.removeClass("active-btn"),K.addClass("active-btn")}),M.click(function(){K.removeClass("active-btn"),J.removeClass("active-btn"),M.addClass("active-btn")}),d="Untitled Card",r="This is the card Description",u="Left",v="Square",f=5,m=P.val(),p.change(function(){d=p.val()}),O.change(function(){r=O.val()}),z.click(function(){u="Left"}),E.click(function(){u="Center"}),F.click(function(){u="Right"}),G.click(function(){v="Circle"}),H.click(function(){v="Square"}),J.click(function(){f=5}),K.click(function(){f=10}),M.click(function(){f=15}),P.change(function(){m=P.val()});let Q=$(".sample-logo-container"),V=$(".logo-align-container"),W=$(".builder-card"),X=$("#stamp-row-1"),Y=$("#stamp-row-2"),Z=$("#stamp-row-3");t.click(function(){console.log("Title: "+d),console.log("Description: "+r),console.log("Logo Alignment: "+u),console.log("Logo Shape: "+v),console.log("Num of stamps: "+f),console.log("Background Colour: "+m),"Square"===v&&(Q.removeClass("circle-logo"),Q.addClass("square-logo")),"Circle"===v&&(Q.removeClass("square-logo"),Q.addClass("circle-logo")),"Left"===u&&(V.removeClass("logo-align-middle"),V.removeClass("logo-align-right"),Q.removeClass("logo-align-middle"),Q.removeClass("m-inline-e"),V.addClass("logo-align-left"),Q.addClass("m-inline-s")),"Center"===u&&(V.removeClass("logo-align-left"),V.removeClass("logo-align-right"),Q.removeClass("m-inline-s"),Q.removeClass("m-inline-e"),V.addClass("logo-align-middle"),Q.addClass("logo-align-middle")),"Right"===u&&(V.removeClass("logo-align-left"),V.removeClass("logo-align-middle"),Q.removeClass("m-inline-s"),Q.removeClass("logo-align-middle"),V.addClass("logo-align-right"),Q.addClass("m-inline-e")),5===f&&(X.show(),Y.hide(),Z.hide(),W.removeClass("two-stamp-row-height"),W.removeClass("three-stamp-row-height"),W.addClass("one-stamp-row-height")),10===f&&(X.show(),Y.show(),Z.hide(),W.removeClass("one-stamp-row-height"),W.removeClass("three-stamp-row-height"),W.addClass("two-stamp-row-height")),15===f&&(X.show(),Y.show(),Z.show(),W.removeClass("one-stamp-row-height"),W.removeClass("two-stamp-row-height"),W.addClass("three-stamp-row-height")),W.css("background-color",m)})});
Video of problem
Loading time is slow because of screen recording
https://imgur.com/a/XcFXXKJ

inject script extension errors javascipt

hello im trying to make an extension for a gambling website its a roullete autobetter .
i have done the logic ( js code) and its working fine . but im facing some problems when i load the extension i receive some errors that i dont understand . and the most important piece code does not run for some reason
some times i does not show some parts of the ui . sometimes the alignments get messed up . its just so inconsistent . these some screenshots of the errors i receive
this the code i write some text above the part that is not executing for some reason (i copy pasted the same part in js injector extension and its working fine so the logic is right) in this part i use a self declare function with setinterval
const box = document.createElement("div");
const f =document.getElementsByClassName("roulette-header")[0]
f.appendChild(box);
box.id = "ExtensionBox";
document.getElementById("ExtensionBox").style.display = "flex"
document.getElementById("ExtensionBox").style.marginTop = "5%"
document.getElementById("ExtensionBox").style.background= "#1c1c22";
document.getElementById("ExtensionBox").style.height = "180px"
document.getElementById("ExtensionBox").style.width = "400%"
document.getElementById("ExtensionBox").style.borderRadius = "5px"
const BaseBet = document.createElement("h1")
BaseBet.innerHTML = "Base Bet"
BaseBet.style.color = "white"
BaseBet.style.position = "relative"
BaseBet.style.left = "40%"
BaseBet.style.fontSize = "2rem"
BaseBet.style.marginTop = "10px"
box.appendChild(BaseBet)
const BasBetInput = document.createElement("input")
BasBetInput.type = "number"
BasBetInput.id = "BasBetInput"
BasBetInput.style.position = "relative"
BasBetInput.style.left = "32%"
BasBetInput.style.fontSize = "1.5rem"
BasBetInput.style.width = "25%"
BasBetInput.style.background = "#2d2d35"
BasBetInput.style.marginTop = "42px"
BasBetInput.placeholder = " 10 recomended "
BasBetInput.style.borderRadius = "5px"
box.appendChild(BasBetInput)
const BaseBet2 = document.createElement("h1")
BaseBet2.innerHTML = "Target Balance"
BaseBet2.style.color = "white"
BaseBet2.style.position = "relative"
BaseBet2.style.left = "7%"
BaseBet2.style.top = "40%"
BaseBet2.style.fontSize = "2rem"
box.appendChild(BaseBet2)
const BasBetInput2 = document.createElement("input")
BasBetInput2.type = "number"
BasBetInput2.id = "BasBetInput2"
BasBetInput2.style.position = "relative"
BasBetInput2.style.left = "-6%"
BasBetInput2.style.fontSize = "1.5rem"
BasBetInput2.style.width = "25%"
BasBetInput2.style.background = "#2d2d35"
BasBetInput2.style.marginTop = "130px"
BasBetInput2.placeholder = " once reached bot will stop "
BasBetInput2.style.borderRadius = "5px"
box.appendChild(BasBetInput2)
const Redbox = document.createElement("div");
f.appendChild(Redbox)
Redbox.id = "redbox"
document.getElementById("redbox").style.display = "flex"
document.getElementById("redbox").style.marginTop = "4%"
document.getElementById("redbox").style.background= "#c8354e";
document.getElementById("redbox").style.height = "180px"
document.getElementById("redbox").style.width = "110%"
document.getElementById("redbox").style.borderRadius = "5px"
Redbox.style.position = "relative"
Redbox.style.right = "71%"
Redbox.style.whiteSpace = "nowrap"
const balanc = document.createElement("h1")
balanc.innerHTML = "Your Starting balance :"+document.getElementById('balance').innerHTML
Redbox.appendChild(balanc)
balanc.id = "balanc"
document.getElementById("balanc").style.color = "white"
document.getElementById("balanc").style.fontSize = "2.5rem"
document.getElementById("balanc").style.marginLeft = "10px"
const message = document.createElement("h2")
message.style.color = "white"
message.innerHTML = " Your can Survive :"
message.style.fontSize = "2.5rem"
message.style.position = "relative"
message.style.top = "75px"
message.style.right = "300px"
message.style.textAlign = "center"
message.id = "message"
Redbox.appendChild(message)
document.getElementById("message").style.fontWeight = "300"
clearbutton = document.createElement("button")
clearbutton.type = "reset"
clearbutton.innerHTML = "reset"
clearbutton.style.height = "50px"
clearbutton.style.width = "10%"
clearbutton.style.color = "white"
clearbutton.style.borderRadius = "5px"
clearbutton.style.fontSize = "1.5rem"
clearbutton.style.background = "#2d2d35"
clearbutton.style.borderColor = "#2d2d35"
clearbutton.style.margin="10px"
clearbutton.style.position = "relative"
clearbutton.style.top="55px"
clearbutton.style.left="16.6%"
box.appendChild(clearbutton)
clearbutton1 = document.createElement("button")
clearbutton1.style.boxShadow = "none"
clearbutton1 = document.createElement("button")
clearbutton1.innerHTML = "indurance"
clearbutton1.style.height = "50px"
clearbutton1.style.width = "10%"
clearbutton1.style.color = "white"
clearbutton1.style.borderRadius = "5px"
clearbutton1.style.fontSize = "1.5rem"
clearbutton1.style.background = "#2d2d35"
clearbutton1.style.borderColor = "#2d2d35"
clearbutton1.style.margin="-30px"
clearbutton1.style.position = "relative"
clearbutton1.style.top="37px"
clearbutton1.style.left="102px"
box.appendChild(clearbutton1)
StartButton = document.createElement("button")
StartButton.id="sb"
box.appendChild(StartButton)
document.getElementById("sb").innerHTML = "Start Bot"
document.getElementById("sb").style.background = "#c8354e"
document.getElementById("sb").style.width = "10%"
document.getElementById("sb").style.height = "50px"
document.getElementById("sb").style.color = "white"
StartButton.style.margin="10px"
StartButton.style.marginTop="122px"
document.getElementById("sb").style.borderColor = "#c8354e"
document.getElementById("sb").style.borderRadius = "5px"
document.getElementById("sb").style.fontSize = "1.5rem"
clearbutton.addEventListener("click",function(){
document.getElementById("BasBetInput2").value = "";
document.getElementById("BasBetInput").value = "";
})
losses = 1;
clearbutton1.addEventListener("click",function(){
balance= document.getElementById('balance').innerHTML;
balance = parseInt(balance.replace(",",""));
if (balance > 120){
bt=document.getElementById("BasBetInput").value;
losses = 1;
do{
bt = bt*2
balance = balance-bt
losses = losses+1
}while (!(bt > balance))
document.getElementById("message").innerHTML = " Your can Survive :"+" "+losses +" "+ "Losses"
}
})
document.getElementById("sb").addEventListener("click",function(){
balance= document.getElementById('balance').innerHTML;
balance = parseInt(balance.replace(",",""));
bt=document.getElementById("BasBetInput").value;
TargetBalance=document.getElementById("BasBetInput2").value;
document.getElementById('bet-input-r').value = bt ;
bc="black";
if (bc == "red" ){
document.getElementById('roulette-btn-red').click();
}else{
document.getElementById('roulette-btn-black').click();
}
setInterval(function(){
// code here does not excute for some reason
balance= document.getElementById('balance').innerHTML;
balance = parseInt(balance.replace(",",""));
if (TargetBalance>balance){
l=((document.getElementsByClassName("roulette-past-queue--previous-rolls-container horizontal-scroll")[0].innerText).length);
f = document.getElementsByClassName("roulette-past-queue--previous-rolls-container horizontal-scroll")[0].innerText;
number = f.substring(f.lastIndexOf("\n"),l);
number = parseInt(number.replace(" ",""));
if (number >= 1 && number <= 7){
LastResult = "red";
};
if (number >= 8 && number <= 14){
LastResult = "black";
};
if (number == 0){
LastResult = "green";
};
if(LastResult == bc){
if (bc == "red"){
document.getElementById('bet-input-r').value = bt ;
document.getElementById('roulette-btn-red').click();
}else{
document.getElementById('bet-input-r').value = bt ;
document.getElementById('roulette-btn-black').click();
};
};
if(LastResult !== bc){
if (bc == "red"){
document.getElementById('bet-btn-double-r').click();
document.getElementById('roulette-btn-black').click();
bc = "black"
}else{
document.getElementById('bet-btn-double-r').click();
document.getElementById('roulette-btn-red').click();
bc = "red"
};
}
}
},32000 );
})
manifest file :
{
"name": "CSGO500 Roullete Autobetter",
"description": "This is a csgo500 roullete strategy autobetter",
"version": "1.0",
"manifest_version": 2,
"content_scripts": [
{
"js": ["foreground.js"],
"matches": ["https://csgo500.com/roulette"]
}
]
}
every problem was fixed by changing const variables to let. now it works fine

Connecting user input to a DOM's value

How can I connect a button which remains constant with a DOM's value in order to capture a user's selection?
I'm creating a taste questionnaire which asks users to select the style they prefer from amongst a series of pairs, and the specific pairings shown will change based upon the items previously selected. I'd like the 'Select' buttons under each set to remain constant so that the entire page doesn't need to be refreshed for each image.
HTML
<table>
<tr><td id="question" colspan=5 > Question 1</td></tr>
<tr><td >
<img src="files/z1.png" name="taste" value="trendy[1]" />
</td> <td >
<img src="files/z2.png" name="taste1" value="classic[1]"/>
</td> </tr>
<tr><td >
<img alt"Select" src="files/Select.jpg" onclick="javascript:manipulateDOM()">
</td><td >
<img alt"Select" src="files/Select.jpg" onclick="javascript:manipulateDOM1()">
</td></tr></table>
Javascript to determine which image is shown based on user input
var NumberOfImages = 7 - 1; //-1 because array members starts from 0
var trendy = new Array(NumberOfImages);
trendy[0] = "files/z1.png";
trendy[1] = "files/z1.png";
trendy[2] = "files/z14.png";
trendy[3] = "files/z4.png";
trendy[4] = "files/z5.png";
trendy[5] = "files/z6.png";
trendy[6] = "files/z9.png";
var imgNumber = 1; //array key of current image
var NumberOfImages = 7 - 1; //-1 because array members starts from 0
var classic = new Array(NumberOfImages);
classic[0] = "files/z2.png";
classic[1] = "files/z2.png";
classic[2] = "files/z12.png";
classic[3] = "files/z3.png";
classic[4] = "files/z3.png";
classic[5] = "files/z7.png";
classic[6] = "files/z8.png";
var classicNumber = 1; //array key of current image
var text = 6 - 1; //-1 because array members starts from 0
var text = new Array;
text[0] = "Question 1"
text[1] = "Question 2"
text[2] = "Question 3"
text[3] = "Question 4"
text[4] = "Question 5"
text[5] = "Question 6"
text[6] = "Question 7"
var textNumber = 1; //array key of current image
function manipulateDOM() {
changeObjects();
NextImage();
}
function changeObjects() {
document.getElementById("question").innerHTML = text[textNumber];
}
function NextImage() {
if (imgNumber < NumberOfImages) //Stop moving forward when we are out of images
{
trendy[0] = trendy[1]; trendy[1] = trendy[2]; trendy[2] = trendy[3]; trendy[2] = trendy[3]; trendy[3] = trendy[4]; trendy[4] = trendy[5]; trendy[5] = trendy[6];
document.images["taste"].src = trendy[imgNumber];
classic[0] = classic[1]; classic[1] = classic[2]; classic[2] = classic[3]; classic[3] = classic[4]; classic[4] = classic[5]; classic[5] = classic[6];
document.images["taste1"].src = classic[imgNumber];
text[0] = text[1]; text[1] = text[2]; text[2] = text[3]; text[3] = text[4]; text[4] = text[5]; text[5] = text[6];
document.getElementById["question"].innerHTML = text[textNumber];
}
}
function manipulateDOM1() {
changeObjects();
NextImage1();
}
function NextImage1() {
if (imgNumber < NumberOfImages) //Stop moving forward when we are out of images
{
trendy[0] = trendy[1]; trendy[1] = trendy[2]; trendy[2] = trendy[3]; trendy[2] = trendy[3]; trendy[3] = trendy[4]; trendy[4] = trendy[5]; trendy[5] = trendy[6];
document.images["taste"].src = trendy[imgNumber];
classic[0] = classic[1]; classic[1] = classic[2]; classic[2] = classic[3]; classic[3] = classic[4]; classic[4] = classic[5]; classic[5] = classic[6];
document.images["taste1"].src = classic[imgNumber];
text[0] = text[1]; text[1] = text[2]; text[2] = text[3]; text[3] = text[4]; text[4] = text[5]; text[5] = text[6];
document.getElementById["question"].innerHTML = text[textNumber];
}
}

Categories

Resources