fix(ui): correct formula pane positioning, remove excessive tab-panel top padding and add shelf life inline
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
const fs = require('fs');
|
||||
|
||||
async function main() {
|
||||
const pages = await fetch('http://localhost:9222/json').then(r => r.json());
|
||||
const meezPage = pages.find(p => p.url.includes('getmeez.com/recipes/'));
|
||||
if (!meezPage) return console.log('No recipe page found');
|
||||
|
||||
const ws = new WebSocket(meezPage.webSocketDebuggerUrl);
|
||||
let id = 1;
|
||||
const send = (method, params = {}) => new Promise((resolve, reject) => {
|
||||
const msgId = id++;
|
||||
const handler = (event) => {
|
||||
const res = JSON.parse(event.data);
|
||||
if (res.id === msgId) {
|
||||
ws.removeEventListener('message', handler);
|
||||
if (res.error) reject(res.error);
|
||||
else resolve(res.result);
|
||||
}
|
||||
};
|
||||
ws.addEventListener('message', handler);
|
||||
ws.send(JSON.stringify({ id: msgId, method, params }));
|
||||
});
|
||||
|
||||
ws.addEventListener('open', async () => {
|
||||
try {
|
||||
const evalRes = await send('Runtime.evaluate', {
|
||||
expression: `(() => {
|
||||
const metaItem = document.querySelector('[data-testid="metaItem"]');
|
||||
if (!metaItem) return { error: 'metaItem not found' };
|
||||
|
||||
const rect = metaItem.getBoundingClientRect();
|
||||
const parent = metaItem.parentElement;
|
||||
const grandParent = parent ? parent.parentElement : null;
|
||||
|
||||
// Find recipe title
|
||||
const title = document.querySelector('h1, [data-testid*="title"], [data-testid*="Title"]');
|
||||
const titleRect = title ? title.getBoundingClientRect() : null;
|
||||
|
||||
// Find ingredients table / container
|
||||
const ingContainer = document.querySelector('[data-testid*="Ingredients"], [data-testid*="RecipeTable"], table, [role="table"]');
|
||||
const ingRect = ingContainer ? ingContainer.getBoundingClientRect() : null;
|
||||
|
||||
// Collect siblings of metaItem
|
||||
const siblings = parent ? Array.from(parent.children).map(c => ({
|
||||
tag: c.tagName,
|
||||
class: c.className,
|
||||
testId: c.getAttribute('data-testid'),
|
||||
rect: c.getBoundingClientRect(),
|
||||
text: c.innerText?.slice(0, 100)
|
||||
})) : [];
|
||||
|
||||
// Collect ancestors up to body
|
||||
const ancestors = [];
|
||||
let cur = metaItem;
|
||||
while (cur && cur !== document.body) {
|
||||
ancestors.push({
|
||||
tag: cur.tagName,
|
||||
class: cur.className,
|
||||
testId: cur.getAttribute('data-testid'),
|
||||
rect: cur.getBoundingClientRect(),
|
||||
style: {
|
||||
display: window.getComputedStyle(cur).display,
|
||||
position: window.getComputedStyle(cur).position,
|
||||
padding: window.getComputedStyle(cur).padding,
|
||||
margin: window.getComputedStyle(cur).margin,
|
||||
gap: window.getComputedStyle(cur).gap
|
||||
}
|
||||
});
|
||||
cur = cur.parentElement;
|
||||
}
|
||||
|
||||
return {
|
||||
metaItemRect: rect,
|
||||
titleRect,
|
||||
ingRect,
|
||||
titleHtml: title?.outerHTML,
|
||||
parentHtml: parent?.outerHTML?.slice(0, 500),
|
||||
siblings,
|
||||
ancestors
|
||||
};
|
||||
})()`,
|
||||
returnByValue: true
|
||||
});
|
||||
|
||||
fs.writeFileSync('scratch/meez_page_layout.json', JSON.stringify(evalRes.result.value, null, 2));
|
||||
console.log('Saved to scratch/meez_page_layout.json');
|
||||
console.log('Ancestors count:', evalRes.result.value.ancestors?.length);
|
||||
console.log('Siblings:\n', evalRes.result.value.siblings?.map(s => `${s.tag} (${s.testId || s.class}): ${s.text?.slice(0, 40)}`));
|
||||
|
||||
ws.close();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
ws.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,74 @@
|
||||
const fs = require('fs');
|
||||
|
||||
async function main() {
|
||||
const pages = await fetch('http://localhost:9222/json').then(r => r.json());
|
||||
const meezPage = pages.find(p => p.url.includes('getmeez.com/recipes/'));
|
||||
if (!meezPage) return console.log('No recipe page found');
|
||||
|
||||
const ws = new WebSocket(meezPage.webSocketDebuggerUrl);
|
||||
let id = 1;
|
||||
const send = (method, params = {}) => new Promise((resolve, reject) => {
|
||||
const msgId = id++;
|
||||
const handler = (event) => {
|
||||
const res = JSON.parse(event.data);
|
||||
if (res.id === msgId) {
|
||||
ws.removeEventListener('message', handler);
|
||||
if (res.error) reject(res.error);
|
||||
else resolve(res.result);
|
||||
}
|
||||
};
|
||||
ws.addEventListener('message', handler);
|
||||
ws.send(JSON.stringify({ id: msgId, method, params }));
|
||||
});
|
||||
|
||||
ws.addEventListener('open', async () => {
|
||||
try {
|
||||
const evalRes = await send('Runtime.evaluate', {
|
||||
expression: `(() => {
|
||||
// Get the entire recipe layout tree from the main container
|
||||
const main = document.querySelector('[data-testid="RoutesBase"]') || document.querySelector('main') || document.querySelector('#root');
|
||||
|
||||
const header = document.querySelector('[data-testid*="Header"], header');
|
||||
const title = document.querySelector('h1, [data-testid*="EditableEntityNameBase_name"]');
|
||||
const titleParent = title ? title.closest('[data-testid*="Header"], div') : null;
|
||||
|
||||
return {
|
||||
titleParentHtml: titleParent ? {
|
||||
tag: titleParent.tagName,
|
||||
class: titleParent.className,
|
||||
testId: titleParent.getAttribute('data-testid'),
|
||||
html: titleParent.outerHTML,
|
||||
style: {
|
||||
padding: window.getComputedStyle(titleParent).padding,
|
||||
margin: window.getComputedStyle(titleParent).margin
|
||||
}
|
||||
} : null,
|
||||
leftColumnChildren: (() => {
|
||||
const leftCol = document.querySelector('[data-testid="RoutesBase_section"]');
|
||||
if (!leftCol) return null;
|
||||
return Array.from(leftCol.children).map(c => ({
|
||||
tag: c.tagName,
|
||||
class: c.className,
|
||||
testId: c.getAttribute('data-testid'),
|
||||
text: c.innerText?.slice(0, 80),
|
||||
html: c.outerHTML.slice(0, 200)
|
||||
}));
|
||||
})()
|
||||
};
|
||||
})()`,
|
||||
returnByValue: true
|
||||
});
|
||||
|
||||
fs.writeFileSync('scratch/meez_structure.json', JSON.stringify(evalRes.result.value, null, 2));
|
||||
console.log('Structure saved.');
|
||||
console.log(JSON.stringify(evalRes.result.value, null, 2));
|
||||
|
||||
ws.close();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
ws.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,148 @@
|
||||
{
|
||||
"metaItemRect": {},
|
||||
"titleRect": {},
|
||||
"ingRect": {},
|
||||
"titleHtml": "<h1 class=\"MuiTypography-root MuiTypography-body1 css-1vpmx5a\" data-testid=\"EditableEntityNameBase_name\">Marinated Pork Belly</h1>",
|
||||
"parentHtml": "<div class=\"MuiGrid2-root MuiGrid2-container MuiGrid2-direction-xs-row jss545 css-wk7muj\"><div class=\"MuiGrid2-root MuiGrid2-container MuiGrid2-direction-xs-row jss837 css-ts6rjh\" data-testid=\"metaItem\"><div class=\"MuiGrid2-root MuiGrid2-container MuiGrid2-direction-xs-row MuiGrid2-grid-sm-auto MuiGrid2-grid-xs-12 jss838 css-19lmllf\"><span class=\"MuiTypography-root MuiTypography-body1 jss839 css-1cmckbm\">Batch Size:</span><button class=\"MuiButtonBase-root MuiButton-root jss851 MuiButton-text Mui",
|
||||
"siblings": [
|
||||
{
|
||||
"tag": "DIV",
|
||||
"class": "MuiGrid2-root MuiGrid2-container MuiGrid2-direction-xs-row jss837 css-ts6rjh",
|
||||
"testId": "metaItem",
|
||||
"rect": {},
|
||||
"text": "Batch Size:\n1x\nYield:\n\n2000\n\ng"
|
||||
}
|
||||
],
|
||||
"ancestors": [
|
||||
{
|
||||
"tag": "DIV",
|
||||
"class": "MuiGrid2-root MuiGrid2-container MuiGrid2-direction-xs-row jss837 css-ts6rjh",
|
||||
"testId": "metaItem",
|
||||
"rect": {},
|
||||
"style": {
|
||||
"display": "flex",
|
||||
"position": "static",
|
||||
"padding": "0px",
|
||||
"margin": "0px 0px 32px",
|
||||
"gap": "0px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "DIV",
|
||||
"class": "MuiGrid2-root MuiGrid2-container MuiGrid2-direction-xs-row jss545 css-wk7muj",
|
||||
"testId": null,
|
||||
"rect": {},
|
||||
"style": {
|
||||
"display": "flex",
|
||||
"position": "static",
|
||||
"padding": "0px",
|
||||
"margin": "0px",
|
||||
"gap": "0px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "DIV",
|
||||
"class": "MuiGrid2-root MuiGrid2-direction-xs-row MuiGrid2-grid-xs-12 css-j5005a",
|
||||
"testId": "SummaryComponent",
|
||||
"rect": {},
|
||||
"style": {
|
||||
"display": "block",
|
||||
"position": "static",
|
||||
"padding": "0px",
|
||||
"margin": "0px",
|
||||
"gap": "normal"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "DIV",
|
||||
"class": "jss414",
|
||||
"testId": "ItemsBase",
|
||||
"rect": {},
|
||||
"style": {
|
||||
"display": "block",
|
||||
"position": "static",
|
||||
"padding": "0px",
|
||||
"margin": "0px",
|
||||
"gap": "normal"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "DIV",
|
||||
"class": "jss345 jss346 jss343 jss356",
|
||||
"testId": "RoutesBase_section",
|
||||
"rect": {},
|
||||
"style": {
|
||||
"display": "flex",
|
||||
"position": "relative",
|
||||
"padding": "0px 36px 35px",
|
||||
"margin": "0px",
|
||||
"gap": "normal"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "DIV",
|
||||
"class": "MuiGrid2-root MuiGrid2-container MuiGrid2-direction-xs-row MuiGrid2-grid-md-6 jss358 jss359 css-1tpndcg",
|
||||
"testId": null,
|
||||
"rect": {},
|
||||
"style": {
|
||||
"display": "flex",
|
||||
"position": "static",
|
||||
"padding": "16px 0px 0px",
|
||||
"margin": "0px",
|
||||
"gap": "0px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "DIV",
|
||||
"class": "MuiGrid2-root MuiGrid2-container MuiGrid2-direction-xs-row jss344 css-1f11l0v",
|
||||
"testId": "RoutesBase",
|
||||
"rect": {},
|
||||
"style": {
|
||||
"display": "flex",
|
||||
"position": "static",
|
||||
"padding": "0px 0px 16px",
|
||||
"margin": "0px",
|
||||
"gap": "0px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "DIV",
|
||||
"class": "MuiGrid2-root MuiGrid2-container MuiGrid2-direction-xs-row css-8sx0qx",
|
||||
"testId": null,
|
||||
"rect": {},
|
||||
"style": {
|
||||
"display": "flex",
|
||||
"position": "static",
|
||||
"padding": "0px",
|
||||
"margin": "0px",
|
||||
"gap": "0px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "DIV",
|
||||
"class": "",
|
||||
"testId": null,
|
||||
"rect": {},
|
||||
"style": {
|
||||
"display": "flex",
|
||||
"position": "static",
|
||||
"padding": "0px",
|
||||
"margin": "0px",
|
||||
"gap": "normal"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "DIV",
|
||||
"class": "",
|
||||
"testId": null,
|
||||
"rect": {},
|
||||
"style": {
|
||||
"display": "flex",
|
||||
"position": "static",
|
||||
"padding": "0px",
|
||||
"margin": "0px",
|
||||
"gap": "normal"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"titleParentHtml": {
|
||||
"tag": "DIV",
|
||||
"class": "MuiGrid2-root MuiGrid2-direction-xs-row css-1n5khr6",
|
||||
"testId": "RecipeName",
|
||||
"html": "<div class=\"MuiGrid2-root MuiGrid2-direction-xs-row css-1n5khr6\" data-testid=\"RecipeName\"><h1 class=\"MuiTypography-root MuiTypography-body1 css-1vpmx5a\" data-testid=\"EditableEntityNameBase_name\">Marinated Pork Belly</h1><span class=\"MuiTypography-root MuiTypography-body1 jss543 css-svpgt\" data-testid=\"EditableEntityName_text\">nicholas ward</span></div>",
|
||||
"style": {
|
||||
"padding": "0px",
|
||||
"margin": "0px"
|
||||
}
|
||||
},
|
||||
"leftColumnChildren": [
|
||||
{
|
||||
"tag": "DIV",
|
||||
"class": "jss414",
|
||||
"testId": "ItemsBase",
|
||||
"text": "Batch Size:\n1x\nYield:\n\n2000\n\ng\n\nCut & Portion the Pork\n\n\n\n1800\n\ng\n\n\tSous Vide Po",
|
||||
"html": "<div data-testid=\"ItemsBase\" class=\"jss414\"><div class=\"MuiGrid2-root MuiGrid2-direction-xs-row MuiGrid2-grid-xs-12 css-j5005a\" id=\"recipe-summary\" data-testid=\"SummaryComponent\"><div class=\"MuiGrid2-"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -239,7 +239,7 @@ const recipeTabIcon=(name:string)=>`<span class="recipe-tab-icon"><svg viewBox="
|
||||
) : (
|
||||
<section class="recipe-view-formula-pane recipe-tab-panel active" data-recipe-panel="formula">
|
||||
<div class="recipe-view-formula">
|
||||
<RecipeCalculator client:load components={calculatorComponents} units={Object.fromEntries(unitMap)} basisUnitId={domainRecipe.scaling?.basis_amount?.unit_id??domainRecipe.yield.amount.unit_id} yieldQuantity={domainRecipe.yield.amount.quantity} yieldUnitId={domainRecipe.yield.amount.unit_id} yieldConversions={domainRecipe.measure_conversions??[]} mode={domainRecipe.scaling?.mode??"amount"} nutrition={nutrition} cost={cost} servings={domainRecipe.yield.servings} showDerived={false} showPercentControls={false}/>
|
||||
<RecipeCalculator client:load components={calculatorComponents} units={Object.fromEntries(unitMap)} basisUnitId={domainRecipe.scaling?.basis_amount?.unit_id??domainRecipe.yield.amount.unit_id} yieldQuantity={domainRecipe.yield.amount.quantity} yieldUnitId={domainRecipe.yield.amount.unit_id} yieldConversions={domainRecipe.measure_conversions??[]} mode={domainRecipe.scaling?.mode??"amount"} nutrition={nutrition} cost={cost} servings={domainRecipe.yield.servings} showDerived={false} showPercentControls={false} shelfLife={shelfLife}/>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
@@ -19,6 +19,7 @@ type Props = {
|
||||
showDerived?: boolean;
|
||||
showPercentControls?: boolean;
|
||||
yieldConversions?: CalculatorItem["measureConversions"];
|
||||
shelfLife?: string | { duration?: { quantity?: number; unit_id?: string }; storage_condition?: string };
|
||||
};
|
||||
|
||||
function itemUnits(item: CalculatorItem, units: Record<string, Unit>) {
|
||||
@@ -47,13 +48,26 @@ function convertItem(quantity: number, fromUnitId: string, toUnitId: string, ite
|
||||
throw new Error(`No reviewed equivalency from ${fromUnitId} to ${toUnitId}`);
|
||||
}
|
||||
|
||||
export default function RecipeCalculator({ components, units, basisUnitId, yieldQuantity, yieldUnitId, nutrition, cost, servings, showDerived = true, showPercentControls = true, yieldConversions = [] }: Props) {
|
||||
export default function RecipeCalculator({ components, units, basisUnitId, yieldQuantity, yieldUnitId, nutrition, cost, servings, showDerived = true, showPercentControls = true, yieldConversions = [], shelfLife }: Props) {
|
||||
const [factor, setFactor] = useState(1);
|
||||
const [calculatePercent,setCalculatePercent]=useState(showPercentControls);
|
||||
const [percentMode,setPercentMode]=useState<"standard"|"bakers">("standard");
|
||||
const [yieldDisplayUnitId, setYieldDisplayUnitId] = useState(yieldUnitId);
|
||||
const [lineUnits, setLineUnits] = useState<Record<string, string>>({});
|
||||
const validFactor = Number.isFinite(factor) && factor >= 0 ? factor : 0;
|
||||
|
||||
const shelfLifeText = useMemo(() => {
|
||||
if (!shelfLife) return null;
|
||||
if (typeof shelfLife === "string") return shelfLife.trim() || null;
|
||||
const q = shelfLife.duration?.quantity;
|
||||
const u = shelfLife.duration?.unit_id;
|
||||
if (q != null && u) {
|
||||
const unitLabel = q === 1 ? u : `${u}s`;
|
||||
return `${q} ${unitLabel}`;
|
||||
}
|
||||
return null;
|
||||
}, [shelfLife]);
|
||||
|
||||
useEffect(() => { window.dispatchEvent(new CustomEvent("recipe-scale-change", { detail:validFactor })); }, [validFactor]);
|
||||
const itemWeight=(item:CalculatorItem)=>{try{return convertItem(item.amount.quantity,item.amount.unit_id,"gram",item,units);}catch{return undefined;}};
|
||||
const total = useMemo(
|
||||
@@ -77,8 +91,8 @@ export default function RecipeCalculator({ components, units, basisUnitId, yield
|
||||
|
||||
return (
|
||||
<section class="calculator" aria-labelledby="formula-heading">
|
||||
<div class="calculator-heading">
|
||||
<label class="basis-input batch-multiplier-field">
|
||||
<div class="calculator-heading" data-testid="metaItem">
|
||||
<div class="basis-input batch-multiplier-field">
|
||||
<span class="scale-label">Batch Size:</span>
|
||||
<span class="recipe-scale-control batch-size-control">
|
||||
<input
|
||||
@@ -91,8 +105,8 @@ export default function RecipeCalculator({ components, units, basisUnitId, yield
|
||||
/>
|
||||
<span class="batch-unit-suffix">x</span>
|
||||
</span>
|
||||
</label>
|
||||
<label class="basis-input finished-yield-field">
|
||||
</div>
|
||||
<div class="basis-input finished-yield-field">
|
||||
<span class="scale-label">Yield:</span>
|
||||
<span class="recipe-scale-control quantity-control">
|
||||
<input
|
||||
@@ -123,7 +137,13 @@ export default function RecipeCalculator({ components, units, basisUnitId, yield
|
||||
</svg>
|
||||
</button>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
{shelfLifeText && (
|
||||
<div class="basis-input shelf-life-field">
|
||||
<span class="scale-label">Shelf Life:</span>
|
||||
<span class="shelf-life-value">{shelfLifeText}</span>
|
||||
</div>
|
||||
)}
|
||||
{showPercentControls && (
|
||||
<div class="calculator-percent-controls">
|
||||
{calculatePercent && (
|
||||
|
||||
+22
-6
@@ -2878,13 +2878,21 @@ input[type="search"]::-webkit-search-results-decoration,
|
||||
border: 0 !important;
|
||||
}
|
||||
.application-body .recipe-view-details > .recipe-tab-panel.active,
|
||||
.application-body .ingredient-view-details > .entity-tab-panel.active,
|
||||
.application-body .recipe-tab-panel.active,
|
||||
.application-body .entity-tab-panel.active {
|
||||
.application-body .ingredient-view-details > .entity-tab-panel.active {
|
||||
display: block;
|
||||
padding-top: 30px !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
.application-body .recipe-view-formula-pane.recipe-tab-panel.active,
|
||||
.application-body .ingredient-main-content.entity-tab-panel.active,
|
||||
.application-body .recipe-tab-panel.active,
|
||||
.application-body .entity-tab-panel.active {
|
||||
display: block;
|
||||
margin: 0 !important;
|
||||
}
|
||||
.application-body .recipe-view-formula-pane.recipe-tab-panel.active {
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
.application-body .immersive-main .recipe-read-shell .recipe-view-formula .calculator { margin-top:0; }
|
||||
|
||||
/* Formula meta row: Batch and Yield controls matching Meez style. */
|
||||
@@ -2895,8 +2903,8 @@ input[type="search"]::-webkit-search-results-decoration,
|
||||
align-items: center !important;
|
||||
justify-content: flex-start !important;
|
||||
flex-wrap: nowrap !important;
|
||||
gap: 24px !important;
|
||||
margin: 0 0 28px 0 !important;
|
||||
gap: 16px !important;
|
||||
margin: 0 0 32px 0 !important;
|
||||
padding: 0 !important;
|
||||
width: 100% !important;
|
||||
overflow-x: auto !important;
|
||||
@@ -2910,7 +2918,7 @@ input[type="search"]::-webkit-search-results-decoration,
|
||||
display: inline-flex !important;
|
||||
align-items: center !important;
|
||||
gap: 0 !important;
|
||||
margin: 0 !important;
|
||||
margin: 0 12px 0 0 !important;
|
||||
padding: 2px 6px 2px 0 !important;
|
||||
color: #050841 !important;
|
||||
font-family: var(--meez-font-sans) !important;
|
||||
@@ -2922,6 +2930,14 @@ input[type="search"]::-webkit-search-results-decoration,
|
||||
flex-shrink: 0 !important;
|
||||
}
|
||||
|
||||
.application-body .recipe-view-formula .calculator-heading .shelf-life-value {
|
||||
font-family: var(--meez-font-sans) !important;
|
||||
font-size: 14px !important;
|
||||
font-weight: 400 !important;
|
||||
color: #050841 !important;
|
||||
white-space: nowrap !important;
|
||||
}
|
||||
|
||||
.application-body .recipe-view-formula .calculator-heading .scale-label,
|
||||
.application-body .immersive-main .recipe-read-shell .recipe-view-formula .calculator-heading .scale-label {
|
||||
display: inline-block !important;
|
||||
|
||||
Reference in New Issue
Block a user