inject container to the root of a page

This commit is contained in:
Greg 2026-07-04 23:09:52 +03:00
parent 60930c4f1a
commit 6d61a34503

View file

@ -12,197 +12,240 @@ TODO:
- readme how to use and debug
*/
const createPricesContainer = () => {
const pricingActionContainers = document.querySelectorAll(".pricing-details");
l("all pricing containers", pricingActionContainers);
let visible;
for (const node of pricingActionContainers) {
if (node.checkVisibility()) {
l("found visible pricing container", node);
visible = node;
break;
}
}
return visible.parentNode.insertBefore(
document.createElement("div"),
visible,
);
};
const isInLibrary = () => document.querySelectorAll(".read-now").length;
const isInLibrary = () =>
// Old layout
document.querySelectorAll(".read-now").length > 0 ||
// New layout
document.querySelectorAll('[data-testid="buypad-readnow"]').length > 0;
const formatPrice = (countryPrice, convertedPrice, isSelected) => {
if (!isSelected) return "SKIP";
if (!isSelected) return "SKIP";
if (countryPrice == undefined) {
return "NOT LOADED";
}
if (countryPrice == undefined) {
return "NOT LOADED";
}
if (convertedPrice) {
return `${countryPrice} => ${convertedPrice?.formatted}`;
}
if (convertedPrice) {
return `${countryPrice} => ${convertedPrice?.formatted}`;
}
return "NOT FOUND";
return "NOT FOUND";
};
const Price = ({ price, toggleCountry, isSelected }) => {
const { convertedPrice, countryCode, countryPrice } = price;
const [isHovered, setHover] = useState(false);
const { convertedPrice, countryCode, countryPrice } = price;
const [isHovered, setHover] = useState(false);
const hover = useCallback(() => setHover(true));
const leave = useCallback(() => setHover(false));
const hover = useCallback(() => setHover(true));
const leave = useCallback(() => setHover(false));
const style = {
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
backgroundColor: isHovered ? "#d7d7d7" : "",
padding: "0 10px",
};
const style = {
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
backgroundColor: isHovered ? "#d7d7d7" : "",
padding: "0 10px",
};
const link = h(
"a",
{
style: { textDecoration: "underline" },
href: bookUrl(countryCode),
target: "_blank",
},
countryCode.toUpperCase(),
);
const link = h(
"a",
{
style: { textDecoration: "underline" },
href: bookUrl(countryCode),
target: "_blank",
},
countryCode.toUpperCase(),
);
const checkbox = h("input", {
type: "checkbox",
checked: isSelected,
onChange: () => toggleCountry(),
});
const checkbox = h("input", {
type: "checkbox",
checked: isSelected,
onChange: () => toggleCountry(),
});
const priceLabel = h(
"p",
{ style: { fontWeight: "bold" } },
formatPrice(countryPrice, convertedPrice, isSelected),
);
const priceLabel = h(
"p",
{ style: { fontWeight: "bold" } },
formatPrice(countryPrice, convertedPrice, isSelected),
);
return h(
"label",
{ style, onMouseEnter: hover, onMouseLeave: leave },
h("div", { style: { display: "flex" } }, checkbox, link),
priceLabel,
);
return h(
"label",
{ style, onMouseEnter: hover, onMouseLeave: leave },
h("div", { style: { display: "flex" } }, checkbox, link),
priceLabel,
);
};
const InLibrary = () =>
h(
"div",
{
style: {
display: "flex",
flexDirection: "column",
border: "1px solid black",
textAlign: "center",
},
},
h("h2", null, "Already in Library!"),
);
h(
"div",
{
style: {
padding: "10px",
textAlign: "center",
},
},
h("h2", null, "Already in Library!"),
);
const Percent = (percent) => {
const spinner = useRef();
const spinner = useRef();
useEffect(() => {
spinner.current.animate(
[{ transform: "rotate(0deg)" }, { transform: "rotate(360deg)" }],
{ iterations: Infinity, duration: 1000 },
);
}, []);
useEffect(() => {
spinner.current.animate(
[{ transform: "rotate(0deg)" }, { transform: "rotate(360deg)" }],
{ iterations: Infinity, duration: 1000 },
);
}, []);
const style = { padding: "10px 10px 0 10px", textAlign: "center" };
const style = { padding: "10px 10px 0 10px", textAlign: "center" };
if (percent == 100) return h("h2", { style }, "all prices are loaded!");
if (percent == 100) return h("h2", { style }, "all prices are loaded!");
return h(
"h2",
{ style },
`${percent}%`,
h("span", {
ref: spinner,
style: {
width: "16px",
height: "16px",
borderRadius: "50%",
display: "inline-block",
borderTop: "3px solid black",
borderRight: "3px solid transparent",
boxSizing: "border-box",
marginLeft: "15px",
},
}),
);
return h(
"h2",
{ style },
`${percent}%`,
h("span", {
ref: spinner,
style: {
width: "16px",
height: "16px",
borderRadius: "50%",
display: "inline-block",
borderTop: "3px solid black",
borderRight: "3px solid transparent",
boxSizing: "border-box",
marginLeft: "15px",
},
}),
);
};
const Header = (state, percentChecked) => {
if (state === "loading") return Percent(percentChecked);
if (state === "loading") return Percent(percentChecked);
if (state === "done")
return h(
"h2",
{ style: { textAlign: "center" } },
"All prices are loaded!",
);
if (state === "done")
return h(
"h2",
{ style: { textAlign: "center" } },
"All prices are loaded!",
);
return h(
"h2",
{ style: { textAlign: "center" } },
'Select countries or leave as is and press "load"',
);
return h(
"h2",
{ style: { textAlign: "center" } },
'Select countries or leave as is and press "Load prices"',
);
};
const Load = (state, load) => {
return h(
"button",
{
disabled: state === "loading",
onClick: load,
type: "button",
style: { backgroundColor: "#91ff91" },
},
state === "loading" ? "Loading..." : "Load prices",
);
return h(
"button",
{
disabled: state === "loading",
onClick: load,
type: "button",
style: { backgroundColor: "#91ff91" },
},
state === "loading" ? "Loading..." : "Load prices",
);
};
const App = () => {
const { isSelected, toggleCountry, selected } = useSelectedCountries();
const { prices, percentChecked, state, load } = usePrices(selected);
const { isSelected, toggleCountry, selected } = useSelectedCountries();
const { prices, percentChecked, state, load } = usePrices(selected);
const [expanded, setExpanded] = useState(false);
l(selected);
const style = {
display: "flex",
flexDirection: "column",
border: "1px solid black",
};
l(selected);
if (isInLibrary()) {
return InLibrary();
}
const toggle = useCallback(() => setExpanded((v) => !v));
return h(
"div",
{ style },
Header(state, percentChecked),
...prices.map((price) =>
h(Price, {
price,
toggleCountry: () => toggleCountry(price.countryCode),
isSelected: isSelected(price.countryCode),
isLoading: state,
}),
),
Load(state, load),
);
if (isInLibrary()) {
return h(
"div",
{
style: {
position: "fixed",
top: "10px",
left: "10px",
zIndex: 99999,
background: "white",
border: "1px solid black",
fontFamily: "sans-serif",
fontSize: "14px",
},
},
InLibrary(),
);
}
const panelStyle = {
position: "fixed",
bottom: "0",
left: "0",
zIndex: 99999,
background: "white",
border: "1px solid black",
fontFamily: "sans-serif",
fontSize: "14px",
maxHeight: expanded ? "100vh" : "auto",
overflowY: expanded ? "auto" : "hidden",
minWidth: "100%",
};
const buttonStyle = {
height: "2.25rem",
background: "white",
border: "1px solid black",
// padding: "6px 12px",
cursor: "pointer",
fontSize: "14px",
fontWeight: "bold",
width: "100%",
};
if (!expanded) {
return h(
"div",
{ style: panelStyle },
h("button", { style: buttonStyle, onClick: toggle }, "PRICES"),
);
}
const listStyle = {
display: "flex",
flexDirection: "column",
};
return h(
"div",
{ style: panelStyle },
h("button", { style: buttonStyle, onClick: toggle }, "CLOSE"),
h(
"div",
{ style: listStyle },
...[
Header(state, percentChecked),
Load(state, load),
...prices.map((price) =>
h(Price, {
price,
toggleCountry: () => toggleCountry(price.countryCode),
isSelected: isSelected(price.countryCode),
isLoading: state,
}),
),
],
),
);
};
l("starting...");
initCache();
render(createElement(App), createPricesContainer());
const container = document.createElement("div");
document.body.appendChild(container);
render(createElement(App), container);