Add timers

This commit is contained in:
Nicholas Jitkoff 2022-07-31 13:05:20 -07:00
parent c97711ccd8
commit 03a071f04c
5 changed files with 181 additions and 46 deletions

View file

@ -79,6 +79,7 @@ window.addEventListener("message", function(e) {
}, false);
function QRCodeURL(url, options) {
if (url.length > 2953) return undefined;
let size = options?.size ?? 547;
let errorCorrection = options?.correction ?? 'L';
let margin = options?.margin?.toString() || "1";

View file

@ -14,8 +14,11 @@ body {
rgba(16, 12, 12, 0.54);
--hover-background-color:
rgba(16, 12, 12, 0.07);
--image-placeholder-color: var(--hover-background-color);
--image-placeholder-color:
var(--hover-background-color);
--background-color:white;
--alt-text-color:
rgb(0, 118, 173);
}
.thumbnail {
@ -39,18 +42,62 @@ body {
/* text-decoration:underline; */
}
.time-button {
/* background-color:cyan; */
white-space: nowrap;
border-radius:1em;
/* padding:0 0.3em; */
/* border:1px solid currentColor; */
text-decoration:underline;
text-decoration-style: dotted;
@media screen {
.timer {
/* background-color:cyan; */
white-space: nowrap;
border-radius: 2px;
padding: 0 0.1em;
margin: 0 -0.1em;
/* text-decoration:underline; */
text-decoration-style: dotted;
box-sizing: border-box;
/* color: var(--background-color); */
border-bottom:1px solid transparent;
border-bottom-color: currentColor;
/* background-color: var(--text-color); */
}
.timer:hover,
.timer.active {
color: var(--background-color);
background-color:var(--text-color);
z-index:100;
/* border:1px solid currentColor; */
position:relative;
border-bottom-color: transparent;
}
.timer.active .label{
opacity:0.0;
}
.timer .countdown{
position:
absolute;
display:
none;
width: 100%;
text-align: center;
left: 0;
}
.timer.active .countdown {
display:inline-block;
}
}
@media print {
.timer {
font-weight:800;
}
}
img.publisher {
max-width: 5em;
max-height: 2em;
/* margin-top: -0.1em; */
/* transform:scale(0.7); */
/* float:
@ -65,11 +112,11 @@ img.publisher {
.columns {
display: flex;
gap: 2em;
gap: 3em;
margin-bottom: 4em;
align-content: center;
justify-content: center;
flex-direction: row;
flex-direction: row-reverse;
margin-top: 1em;
}
@ -136,15 +183,29 @@ span.number {
/* background: black; */
}
.complete span.number,
li:hover span.number {
color:transparent;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='48' width='48'%3E%3Cpath d='M18.9 36.9 6.5 24.5 9.9 21.1 18.9 30.1 38.05 10.95 41.45 14.35Z'/%3E%3C/svg%3E"); background-size:cover;
@media screen {
.complete span.number, li:hover span.number {
color:transparent;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='48' width='48'%3E%3Cpath d='M18.9 36.9 6.5 24.5 9.9 21.1 18.9 30.1 38.05 10.95 41.45 14.35Z'/%3E%3C/svg%3E"); background-size:cover;
}
.complete span.number {
border-color:transparent;
}
.complete span.number {
border-color:transparent;
}
.complete .noun {
color:var(--disabled-text-color);
}
.ingredient.complete,
li.complete {
text-decoration: line-through;
color:var(--disabled-text-color);
text-decoration: line-through;
/*noinspection CssOverwrittenProperties*/
text-decoration: rgba(0,0,0,0.75) solid 0.5px line-through; /* Ignored in CSS1/CSS2 UAs */
}
}
.number.big {
@ -212,22 +273,20 @@ li:hover {
.ingredients {
font-weight: 600;
font-size: 88%;
/* position:
sticky; */
position: sticky;
/* top: 0px; */
/* padding-top: 1.5em; */
flex: 0 1 35%;
/* color: rgba(0,0,0,0.8); */
/* color:rgb(0, 118, 173); */
/* color:
rgb(0, 118, 173); */
}
.noun {
font-weight: 700;
color: var(--opaque-text-color);
}
.complete .noun {
color:var(--disabled-text-color);
color: var(--alt-text-color);
}
li:hover .noun {
text-decoration: 1px dotted underline;
/* background-color: var(--hover-background-color); */
@ -274,16 +333,6 @@ li:hover .noun {
right;
}
.ingredient.complete,
li.complete {
text-decoration: line-through;
color:var(--disabled-text-color);
text-decoration: line-through;
/*noinspection CssOverwrittenProperties*/
text-decoration: rgba(0,0,0,0.75) solid 0.5px line-through; /* Ignored in CSS1/CSS2 UAs */
}
.ingredient:hover {
opacity: 1.0;
background-color: rgba(128, 128, 128, 0.1)
@ -291,7 +340,7 @@ text-decoration: rgba(0,0,0,0.75) solid 0.5px line-through; /* Ignored in CSS1/C
.step {
max-width: 80em;
cursor: pointer;
cursor: default;
/* border:1px solid red; */
margin-bottom: 1em;
}
@ -422,6 +471,7 @@ a.action .icon {
user-select: none;
/* float: left; */
display: inline-block;
-webkit-print-color-adjust: exact;
}
@ -431,7 +481,7 @@ a.action .icon {
min-height:100vh;
}
.print-hide {
display: none;
display: none !important;
}
@page {
@ -544,7 +594,7 @@ img.qr {
width: 16px;
height: 16px;
}
.icon.timer {
.icon.time {
background-position: -72px -16px;
width: 16px;
height: 16px;
@ -647,4 +697,12 @@ a.publisherlink {
/* order: -2; */
/* width: 100%; */
float: left;
}
.headerleft h1 {
max-width: 16em;
}
span.timer.active.expired {
background-color: rgb(221, 40, 0);
}

View file

@ -265,6 +265,7 @@ function highlightStep(e) {
// el.classList.toggle("complete", )
// }
if (e.target.classList.contains("noun")) return;
if (e.target.closest(".timer")) return;
if (e.target.tagName == "A") return;
e.target.closest("li").classList.toggle("complete")
@ -293,12 +294,67 @@ function highlightTerms(string, terms) {
}
function highlightTimes(string) {
const pattern = /([0-9]+) ?(minutes|hours)/g;
return string.replace(pattern, match => {
return `<span class="time-button" onclick="setTimer(${match})">${match}</span>`
const pattern = /([0-9]+)\s*(?:to|-|)?\s*([0-9]+)?\s?(minutes?|hours?)/g;
return string.replace(pattern, (match, t1, t2, tt, offset, groups) => {
// console.log("match", t1, t2, tt)
return `<span class="timer" onclick="startTimer(this, ${t1}, ${t2})"><span class="countdown"></span><span class="label">${match}</span></span>`
});
}
function dismissTimer(e) {
console.log(e.target);
}
function playSound(loc) {
var audio = new Audio(loc);
// audio.play();
}
function startTimer(e, t1, t2) {
if (!e.startTime) {
e.startTime = new Date();
let duration = t1 * 60
e.endTime = new Date(e.startTime.getTime() + duration * 1000);
console.log("end", e.endTime);
let countdownEl = e.querySelector(".countdown");
console.log(e, t1, t2);
e.classList.add("active")
let update = () => {
let now = new Date();
let percent = (now - e.startTime) / 1000 / duration * 100;
// console.log("perc", (now - e.startTime)/1000 , percent, duration)
e.style.backgroundImage = `linear-gradient(to right, transparent ${percent}%, rgba(255,255,255,0.4) ${percent}%)`
let remaining = Math.round((e.endTime - now)/1000);
let expired = percent > 100;
if (expired) {
playSound("recipe/beep.mp4")
delete e.style.backgroundImage;
// clearInterval(e.interval);
remaining = -remaining;
e.classList.add("expired")
}
let countdownString = `${expired ? "-": ""}${Math.floor(remaining / 60)}m ${remaining % 60}s`
countdownEl.innerText = countdownString;
}
e.interval = setInterval(update, 1000)
update();
} else {
e.interval = clearInterval(e.interval)
e.classList.remove("active");
e.classList.remove("expired");
e.style.backgroundImage = '';
delete e.startTime;
}
}
window.startTimer = startTimer;
function share() {
parent.postMessage({share:{}}, "*");
}
@ -342,7 +398,6 @@ function render() {
let favicon = faviconForTitle(title) || "🍴";
parent.postMessage({title:title, favicon:favicon, image:image, description:description, wakeLock:true, updateURL:true}, "*");
description = description.split("\n").join("<p>")
console.log(description)
// let text = instructions.join(" ");
let ingredients = json.recipeIngredient;
@ -386,6 +441,24 @@ function render() {
m("span.substep",{innerHTML:highlightTimes(highlightTerms(FRACTION_MAP.replace(text.trim()), terms))}))
}
function stepsFromText(text) {
let steps = []
let components = text.split(/(?<=[\.\!\?]|[\.\!\?]\)|\))\s+(?!\()/);
let append = false;
while (components.length) {
let step = components.shift()
if (append) {
steps.push(steps.pop() + " " + step)
} else {
steps.push(step);
}
if (step.indexOf("(") >= 0) { append = true; }
if (step.includes(")")) { append = false; }
}
return steps;
}
function flattenInstructions(instruction) {
if (instruction.itemListElement) {
return ["= " + instruction.name].concat(flattenInstructions(instruction.itemListElement).flat());
@ -400,7 +473,7 @@ function render() {
console.log()
if (reformat) {
text = text.replace(/(\.\)? )+/g,"$1\n").split("\n")///text.match( /[0-9\. ]{0,6}[^\.!\?]+[\.!\?]+/g );
text = stepsFromText(text);
} else {
text = text.match( /[^\n]+/g );
}
@ -436,13 +509,16 @@ function render() {
bgImg.onload = function(){
let thumbnail = document.querySelector("#thumbnail");
let thumbnailContainer = document.querySelector("#thumbnail-container");
thumbnail.style.backgroundImage = 'url(' + bgImg.src + ')';
thumbnail.style.backgroundImage = 'url("' + bgImg.src + '")';
thumbnail.style.filter = `blur(${thumbnailContainer.offsetHeight / bgImg.naturalHeight * 1}px)`;
thumbnail.style.transform = `scale(1.1)`;
setTimeout(() => thumbnail.style.opacity = 1.0, 100);
};
bgImg.src = image;
console.log("image", bgImg.src)
let qrImage = QRCodeURL(params.originalURL, {margin:0});
let originalURL = json.mainEntityOfPage?.["@id"] || json.mainEntityOfPage || json.url;
document.body.appendChild(
m(".recipe", {},
@ -458,7 +534,7 @@ function render() {
m(".metadata",
(recipeYield) ? m("div", m("span.yield", m(".icon.servings"), recipeYield)) : null,
json.totalTime ? m(".time",
m(".icon.timer"),
m(".icon.time"),
json.totalTime ? m("span", formatTime(json.totalTime)) : undefined,
// " (",
@ -496,7 +572,7 @@ function render() {
m("section.ingredients",
m("caption.ingredients-title", "Ingredients"),
ingredients,
m("img.qr.print-show", {src:QRCodeURL(params.originalURL, {margin:0})})
qrImage ? m("img.qr.print-show", {src:qrImage}) : null
),
m(reformat ? "section.instructions.numbered" : "section.instructions",
m("caption.ingredients-title", {onclick:() => {reformat = !reformat; render(); return false;}},

BIN
docs/render/recipe/beep.mp4 Normal file

Binary file not shown.

Binary file not shown.