// Contracts tab: opportunities to seize, active effects, the active contract // and the board of offers. import { t } from '../services/i18n.js'; import { h } from './dom.js'; import { formatDuration, moneyFormatter, rewardText, setText } from './text.js'; const BUFF_LABEL = { demand_mult: 'contracts.buff.demand', global_mult: 'contracts.buff.production', material_price_mult: 'contracts.buff.material', }; export function createContractsView(root, actions) { let ctx; let fmt; let els; let offersSignature = ''; let buffsSignature = ''; function rebuild(newCtx, formatter) { ctx = newCtx; fmt = formatter; offersSignature = ''; buffsSignature = ''; els = { eventTitle: h('h3', { class: 'card__title' }), eventText: h('p', { class: 'card__desc' }), eventTime: h('p', { class: 'card__meta' }), claim: h('button', { class: 'btn btn--primary', type: 'button', text: t('contracts.claim'), onclick: () => actions.claimEvent() }), eventBox: h('div', { class: 'callout' }), eventNone: h('p', { class: 'hint', text: t('contracts.eventNone') }), buffs: h('ul', { class: 'cards' }), activeBox: h('div', { class: 'callout' }), activeTitle: h('h3', { class: 'card__title' }), activeReward: h('p', { class: 'card__desc' }), activeProgress: h('progress', { class: 'progress', max: 1, value: 0 }), activeText: h('p', { class: 'card__meta' }), abandon: h('button', { class: 'btn', type: 'button', text: t('contracts.abandon'), onclick: () => actions.abandon() }), activeNone: h('p', { class: 'hint', text: t('contracts.activeNone') }), offers: h('ul', { class: 'cards' }), offersNone: h('p', { class: 'hint', text: t('contracts.offersNone') }), }; els.eventBox.append(els.eventTitle, els.eventText, els.eventTime, els.claim); els.activeBox.append(els.activeTitle, els.activeReward, els.activeProgress, els.activeText, els.abandon); root.replaceChildren( h( 'section', { class: 'panel' }, h('h2', { class: 'panel__title', text: t('contracts.events') }), els.eventBox, els.eventNone, h('h3', { class: 'dialog__subtitle', text: t('contracts.effects') }), els.buffs, ), h( 'section', { class: 'panel' }, h('h2', { class: 'panel__title', text: t('contracts.active') }), els.activeBox, els.activeNone, h('h3', { class: 'dialog__subtitle', text: t('contracts.board') }), h('p', { class: 'hint', text: t('contracts.boardInfo', { seconds: ctx.rules.contracts.offerEverySeconds }) }), els.offers, els.offersNone, ), ); } function eventText(type) { const cfg = ctx.rules.events.types.find((e) => e.id === type); return t(`event.${type}.text`, { value: cfg?.buff ? fmt.number(cfg.buff.value) : '', seconds: cfg?.buff ? cfg.buff.seconds : '', }); } function updateEvent(state) { const current = state.events.current; els.eventBox.hidden = !current; els.eventNone.hidden = Boolean(current); if (!current) return; setText(els.eventTitle, t(`event.${current.type}.title`)); setText(els.eventText, eventText(current.type)); setText(els.eventTime, t('contracts.timeLeft', { time: formatDuration(current.remaining) })); } function updateBuffs(state) { const signature = state.events.buffs.map((b) => `${b.type}:${b.value}`).join(); if (signature !== buffsSignature) { buffsSignature = signature; els.buffs.replaceChildren( ...state.events.buffs.map((b) => h( 'li', { class: 'card' }, h('div', { class: 'card__body' }, h('h3', { class: 'card__title', text: t(BUFF_LABEL[b.type], { value: fmt.number(b.value) }) }), h('p', { class: 'card__meta' })), ), ), ); } [...els.buffs.children].forEach((li, i) => { const buff = state.events.buffs[i]; if (buff) setText(li.querySelector('.card__meta'), t('contracts.timeLeft', { time: formatDuration(buff.remaining) })); }); } function updateActive(state) { const active = state.contracts.active; els.activeBox.hidden = !active; els.activeNone.hidden = Boolean(active); if (!active) return; const { offer } = active; setText(els.activeTitle, t(`contracts.template.${offer.template}`)); setText(els.activeReward, rewardText(offer.reward, fmt, offer.seconds)); els.activeProgress.max = offer.target; els.activeProgress.value = active.progress; setText( els.activeText, t('contracts.progress', { done: fmt.number(Math.floor(active.progress)), target: fmt.number(offer.target), time: formatDuration(active.remaining), }), ); } function updateOffers(state) { const offers = state.contracts.offers; const signature = offers.map((o) => `${o.template}:${o.target}:${o.reward.type}:${o.reward.amount}`).join(); if (signature !== offersSignature) { offersSignature = signature; els.offers.replaceChildren( ...offers.map((offer, index) => h( 'li', { class: 'card' }, h( 'div', { class: 'card__body' }, h('h3', { class: 'card__title', text: t(`contracts.template.${offer.template}`) }), h('p', { class: 'card__desc', text: t('contracts.goal', { target: fmt.number(offer.target), duration: formatDuration(offer.seconds) }) }), h('p', { class: 'card__meta', text: rewardText(offer.reward, fmt, offer.seconds) }), ), h('button', { class: 'btn btn--buy', type: 'button', text: t('contracts.accept'), 'data-index': index, onclick: () => actions.accept(index) }), ), ), ); } els.offersNone.hidden = offers.length > 0; for (const button of els.offers.querySelectorAll('button')) button.disabled = Boolean(state.contracts.active); } function update(state) { updateEvent(state); updateBuffs(state); updateActive(state); updateOffers(state); } return { rebuild, update }; }