DailyTasksView.tsx 10.7 Ko
Télécharger le fichier
import { AddLinkEventTracker, CreateLinkEvent, DailyTaskData, GetSessionDataManager, ILinkEventTracker, RemoveLinkEventTracker } from '@octane/renderer';
import { FC, useEffect, useMemo, useState } from 'react';
import { getDailyTaskImageUrl, getDailyTaskProgressPercent, getDailyTaskRewardIcon, getDailyTaskStyle, getDailyTasksWindowCaption, localizeWithFallback } from '../../api';
import { Button, DraggableWindowPosition, LayoutBadgeImageView, LayoutCurrencyIcon, LayoutFurniIconImageView, LayoutProgressBar, Text } from '../../common';
import { useDailyTasks } from '../../hooks';
import { OctaneCard } from '../../layout';
import { Hint } from '../../common/Tooltip';
import { NativeTextTree } from '../../common/native-text/NativeTextTree';
/** One row of the official daily tasks list (task_template 402x119). */
const DailyTaskRowView: FC<{ task: DailyTaskData; onClaim: (task: DailyTaskData) => void }> = ({ task, onClaim }) => {
const [claiming, setClaiming] = useState(false);
const style = getDailyTaskStyle(task.status, task.isBonus);
const inProgress = task.status === DailyTaskData.STATUS_IN_PROGRESS;
const claimed = task.status === DailyTaskData.STATUS_CLAIMED;
useEffect(() => setClaiming(false), [task.status]);
return (
<NativeTextTree>
<div className={`octane-daily-task octane-daily-task-${style}`}>
<div className="octane-daily-task-left">
<div className="octane-daily-task-title">
<span>{localizeWithFallback(task.nameLocalizationKey, task.taskCode)}</span>
<Hint content={localizeWithFallback(task.hintLocalizationKey, '')}>
<span aria-label={localizeWithFallback(task.hintLocalizationKey, '')} className="octane-daily-task-hint" role="img">
?
</span>
</Hint>
</div>
<div className="octane-daily-task-body">
<img className="octane-daily-task-image" src={getDailyTaskImageUrl(task)} alt="" draggable={false} />
<Text className="octane-daily-task-desc">{localizeWithFallback(task.descriptionLocalizationKey, '')}</Text>
</div>
{inProgress && (
<LayoutProgressBar
className="octane-daily-task-progress"
progress={getDailyTaskProgressPercent(task.repeats, task.requiredRepeats)}
maxProgress={100}
text={localizeWithFallback(
'quests.tracker.progress',
'%progress% / %limit%',
['progress', 'limit'],
[String(task.repeats), String(task.requiredRepeats)]
)}
/>
)}
{!inProgress && <div className="octane-daily-task-complete">{localizeWithFallback('dailytasks.task.complete', 'Task completed!')}</div>}
</div>
<div className="octane-daily-task-right">
<div className="octane-daily-task-reward-title">{localizeWithFallback('dailytasks.reward', 'Reward')}</div>
<div className="octane-daily-task-rewards">
{task.rewards.map((reward, index) => {
const icon = getDailyTaskRewardIcon(reward);
return (
<div key={index} className="octane-daily-task-reward">
{icon?.kind === 'currency' && <LayoutCurrencyIcon type={icon.type} className="octane-daily-task-reward-icon" />}
{icon?.kind === 'badge' && <LayoutBadgeImageView badgeCode={icon.code} />}
{icon?.kind === 'furni' && (
<LayoutFurniIconImageView productType={icon.productType} productClassId={icon.classId} className="octane-daily-task-reward-icon" />
)}
{!icon && <div className="octane-daily-task-reward-generic">{reward.rewardTypeId}</div>}
{reward.amount > 1 && <span className="octane-daily-task-reward-amount">x{reward.amount}</span>}
</div>
);
})}
</div>
{!inProgress && (
<Button
variant="success"
className="octane-daily-task-claim"
disabled={claimed || claiming}
onClick={() => {
setClaiming(true);
onClaim(task);
}}
>
{claimed ? localizeWithFallback('dailytasks.claimed', 'Claimed') : localizeWithFallback('dailytasks.claim', 'Claim')}
</Button>
)}
</div>
</div>
</NativeTextTree>
);
};
/**
* The official daily tasks window (428x553, `dailytasks/open`): the tasks of the day, the unclaimed
* reminder for expired-but-completed tasks and the HC double-duckets footer.
*/
export const DailyTasksView: FC<{}> = () => {
const [isVisible, setIsVisible] = useState(false);
const [showUnclaimed, setShowUnclaimed] = useState(false);
const [, setTick] = useState(0);
const { activeTasks = [], unclaimedTasks = [], requestTasks = null, claimTask = null } = useDailyTasks();
const hasClub = GetSessionDataManager().clubLevel > 0;
useEffect(() => {
const linkTracker: ILinkEventTracker = {
linkReceived: (url: string) => {
const parts = url.split('/');
if (parts.length < 2) return;
switch (parts[1]) {
case 'open':
case 'show':
setIsVisible(true);
return;
case 'hide':
setIsVisible(false);
return;
case 'toggle':
setIsVisible((prevValue) => !prevValue);
return;
}
},
eventUrlPrefix: 'dailytasks/'
};
AddLinkEventTracker(linkTracker);
return () => RemoveLinkEventTracker(linkTracker);
}, []);
useEffect(() => {
if (!isVisible) return;
requestTasks && requestTasks();
const interval = window.setInterval(() => setTick((prevValue) => prevValue + 1), 1000);
return () => window.clearInterval(interval);
}, [isVisible, requestTasks]);
const maxSecondsLeft = useMemo(() => activeTasks.reduce((max, task) => Math.max(max, task.secondsLeft), 0), [activeTasks]);
useEffect(() => {
if (!isVisible || !requestTasks) return;
if (!activeTasks.length || maxSecondsLeft < -5) requestTasks();
}, [isVisible, activeTasks.length, maxSecondsLeft, requestTasks]);
if (!isVisible) return null;
const onClaim = (task: DailyTaskData) => claimTask && claimTask(task.taskId);
return (
<NativeTextTree>
<>
<OctaneCard className="octane-daily-tasks" uniqueKey="daily-tasks" windowPosition={DraggableWindowPosition.TOP_CENTER} offsetTop={-30}>
<OctaneCard.Header headerText={getDailyTasksWindowCaption(maxSecondsLeft)} onCloseClick={() => setIsVisible(false)} />
<OctaneCard.Content className="octane-daily-tasks-content">
{unclaimedTasks.length > 0 && (
<div className="octane-daily-tasks-extra">
<Button
variant="secondary"
className="octane-daily-tasks-unclaimed-button"
title={localizeWithFallback('dailytasks.unclaimed.tooltip', 'Rewards of earlier days you have not claimed yet')}
onClick={() => setShowUnclaimed(true)}
>
{localizeWithFallback('dailytasks.unclaimed', 'Unclaimed rewards')}
</Button>
</div>
)}
<div className="octane-daily-tasks-list">
{activeTasks.map((task) => (
<DailyTaskRowView key={task.taskId} task={task} onClaim={onClaim} />
))}
</div>
<div className="octane-quests-hc-info">
<Text className="octane-quests-hc-text">
{hasClub
? localizeWithFallback('hc.has.double_duckets.info', 'You get double duckets as you are an HC member!')
: localizeWithFallback('hc.get.double_duckets.info', 'Get HC membership to gain double duckets!')}
</Text>
{!hasClub && (
<Button variant="success" className="octane-quests-get-hc" onClick={() => CreateLinkEvent('catalog/open/hc_membership')}>
{localizeWithFallback('generic.get_hc', 'Get HC')}
</Button>
)}
</div>
</OctaneCard.Content>
</OctaneCard>
{showUnclaimed && (
<OctaneCard
className="octane-daily-tasks octane-daily-tasks-unclaimed"
uniqueKey="daily-tasks-unclaimed"
windowPosition={DraggableWindowPosition.CENTER}
>
<OctaneCard.Header
headerText={localizeWithFallback('dailytasks.unclaimed', 'Unclaimed rewards')}
onCloseClick={() => setShowUnclaimed(false)}
/>
<OctaneCard.Content className="octane-daily-tasks-content">
<div className="octane-daily-tasks-list">
{unclaimedTasks.map((task) => (
<DailyTaskRowView key={task.taskId} task={task} onClaim={onClaim} />
))}
</div>
</OctaneCard.Content>
</OctaneCard>
)}
</>
</NativeTextTree>
);
};
