5180 fichiers 38.5 Mo 0 téléchargements 242 vues mis à jour il y a 5 heures ui
nitro-v36-with-renderer-v36 / src / components / talent / TalentTrackView.tsx
TalentTrackView.tsx 15.1 Ko Télécharger le fichier
import { GetSessionDataManager } from '@octane/renderer';
import { FC, useEffect, useRef } from 'react';
import {
    GetConfigurationValue,
    getAchievementBaseCode,
    getCurrentLevelIndex,
    getTalentLevelImageUrl,
    getTalentProductImageUrl,
    getTalentTrackProgress,
    ITalentLevel,
    ITalentTask,
    ITalentTrack,
    LocalizeBadgeDescription,
    LocalizeBadgeName,
    localizeWithFallback,
    SanitizeHtml,
    splitTalentTasks,
    TALENT_STATE_ACHIEVED,
    TALENT_STATE_IN_PROGRESS,
    TALENT_STATE_LOCKED,
    TALENT_TRACK_CITIZENSHIP
} from '../../api';
import avatarGlow from '../../assets/images/talent/talent_avatar_glow.png';
import checkMark from '../../assets/images/talent/talent_check_mark_circle.png';
import citizenshipAccomplished from '../../assets/images/talent/talent_citizenship_accomplished.png';
import lockedAchievement from '../../assets/images/talent/talent_locked_achievement.png';
import lockedStripe from '../../assets/images/talent/talent_locked_stripe.png';
import maskLeft from '../../assets/images/talent/talent_mask_left.png';
import maskRight from '../../assets/images/talent/talent_mask_right.png';
import vipReward from '../../assets/images/talent/talent_vip_reward.png';
import { AirModalDialog, LayoutAvatarImageView, LayoutBadgeImageView } from '../../common';
import { BaseButton } from '../../common/BaseButton';
import { NativeTextTree } from '../../common/native-text/NativeTextTree';
import { LibraryImageView } from '../help/habbo-way/HabboWayPagerView';

export const getTalentTaskTitle = (badgeCode: string): string => LocalizeBadgeName(badgeCode).toUpperCase();

/** The task's text; a %limit% the badge texts leave open is the task's goal (its total score). */
export const getTalentTaskInstruction = (badgeCode: string, limit?: number): string => {
    const text = localizeWithFallback(`badge_instruction_${getAchievementBaseCode(badgeCode)}`, LocalizeBadgeDescription(badgeCode));

    return limit > 0 ? text.replaceAll('%limit%', String(limit)) : text;
};

// AIR formatted_text: the begin texts carry small HTML such as <br>.
const FormattedText: FC<{ className: string; text: string }> = ({ className, text }) => (
    <NativeTextTree>
        <div className={className} dangerouslySetInnerHTML={{ __html: SanitizeHtml(text) }} />
    </NativeTextTree>
);

// progress_container: the needle starts at x36 (its middle at 52) and the bar keeps the same inset on the right.
const PROGRESS_INSET = 52;

export const talentProgressPosition = (progress: number): string =>
    `calc(${PROGRESS_INSET}px + (100% - ${PROGRESS_INSET * 2}px) * ${Math.min(1, Math.max(0, progress))})`;

interface TalentTrackViewProps {
    track: ITalentTrack;
    onClose: () => void;
    onSelectTask: (task: ITalentTask) => void;
    onOpenTrack: (name: string) => void;
}

const TalentTaskView: FC<{ task: ITalentTask; onSelect: (task: ITalentTask) => void }> = ({ task, onSelect }) => {
    const ongoing = task.state === TALENT_STATE_IN_PROGRESS;
    const progress = task.totalScore > 0 ? Math.min(1, task.currentScore / task.totalScore) : 0;
    const body = (
        <>
            <div className="octane-talent-task__badge">
                {task.state === TALENT_STATE_LOCKED ? <img alt="" src={lockedAchievement} /> : <LayoutBadgeImageView badgeCode={task.badgeCode} />}
            </div>
            <div className="octane-talent-task__copy">
                <div className="octane-talent-task__title">{getTalentTaskTitle(task.badgeCode)}</div>
                <div className="octane-talent-task__description">{getTalentTaskInstruction(task.badgeCode, task.totalScore)}</div>
            </div>
            {ongoing && (
                <div className="octane-talent-task__progress">
                    <span style={{ width: `${progress * 100}%` }} />
                </div>
            )}
            {task.state === TALENT_STATE_ACHIEVED && <img alt="" className="octane-talent-task__check" src={checkMark} />}
        </>
    );

    if (ongoing) {
        return (
            <NativeTextTree>
                <BaseButton className="octane-talent-task is-ongoing" type="button" onClick={() => onSelect(task)}>
                    {body}
                </BaseButton>
            </NativeTextTree>
        );
    }

    return <div className={`octane-talent-task ${task.state === TALENT_STATE_ACHIEVED ? 'is-achieved' : 'is-locked'}`}>{body}</div>;
};

const TalentLevelView: FC<{ trackName: string; level: ITalentLevel; onSelectTask: (task: ITalentTask) => void }> = ({ trackName, level, onSelectTask }) => {
    const library = GetConfigurationValue<string>('image.library.url', '');
    const [top, bottom] = splitTalentTasks(level.tasks);
    const achieved = level.state === TALENT_STATE_ACHIEVED;
    const hasRewards = level.perks.length > 0 || level.products.length > 0;

    return (
        <NativeTextTree>
            <div className="octane-talent-level" data-state={level.state}>
                <div className="octane-talent-level__title">{localizeWithFallback(`talent.track.${trackName}.level.${level.level}.title`, '')}</div>
                <div className="octane-talent-level__description">{localizeWithFallback(`talent.track.${trackName}.level.${level.level}.description`, '')}</div>
                <LibraryImageView className="octane-talent-level__illustration" src={getTalentLevelImageUrl(library, trackName, level.level)} />
                <div className="octane-talent-level__status">
                    <div className={`octane-talent-reward ${achieved ? 'is-achieved' : 'is-locked'}`}>
                        <div className="octane-talent-reward__border">
                            {achieved && <div className="octane-talent-reward__unlocked">{localizeWithFallback('talent.track.common.unlocked', 'UNLOCKED')}</div>}
                            <div className="octane-talent-reward__description">{localizeWithFallback(`talent.track.${trackName}.level.${level.level}.unlock`, '')}</div>
                            {hasRewards && (
                                <div className="octane-talent-reward__list">
                                    {level.products.map((product, index) =>
                                        product.vipDays > 0 ? (
                                            <div key={`vip-${index}`} className="octane-talent-reward__item is-vip">
                                                <img alt="" src={vipReward} />
                                                <span>{product.vipDays}</span>
                                            </div>
                                        ) : (
                                            <div key={`product-${index}`} className="octane-talent-reward__item">
                                                <div className="octane-talent-reward__product">
                                                    <LibraryImageView src={getTalentProductImageUrl(library, product.productCode)} />
                                                </div>
                                            </div>
                                        )
                                    )}
                                    {level.perks.map((perk) => (
                                        // reward_achieved / reward_locked: a 60x60 icon, then the perk's name and description.
                                        <div key={perk} className="octane-talent-reward__item is-perk">
                                            <div className="octane-talent-reward__perk-icon">
                                                <img alt="" src={achieved ? checkMark : lockedAchievement} />
                                            </div>
                                            <div className="octane-talent-reward__perk-copy">
                                                <span className="octane-talent-reward__perk-title">{localizeWithFallback(`perk.${perk}.name`, perk)}</span>
                                                <span>{localizeWithFallback(`perk.${perk}.description`, '')}</span>
                                            </div>
                                        </div>
                                    ))}
                                </div>
                            )}
                        </div>
                        {!achieved && <img alt="" className="octane-talent-reward__stripe" src={lockedStripe} />}
                        {achieved && <img alt="" className="octane-talent-reward__check" src={checkMark} />}
                    </div>
                    <div className="octane-talent-level__tasks">
                        {[top, bottom].map((row, index) => (
                            <div key={index} className="octane-talent-level__task-row">
                                {row.map((task) => (
                                    <TalentTaskView key={task.id} task={task} onSelect={onSelectTask} />
                                ))}
                            </div>
                        ))}
                    </div>
                </div>
            </div>
        </NativeTextTree>
    );
};

/**
 * Official TalentTrackController (`talent_track` layout): a panorama of the track's levels, each
 * with its reward and its tasks, over the progress bar with the avatar needle. With citizenship on,
 * the helper track starts on the "citizenship accomplished" pane.
 */
export const TalentTrackView: FC<TalentTrackViewProps> = ({ track, onClose, onSelectTask, onOpenTrack }) => {
    const panoramaRef = useRef<HTMLDivElement>(null);
    const citizenshipEnabled = GetConfigurationValue<boolean>('talent.track.citizenship.enabled', true);
    const progress = getTalentTrackProgress(track.levels);
    const currentLevel = getCurrentLevelIndex(track.levels);
    const figure = GetSessionDataManager().figure;
    const name = track.name;

    // scrollToLevel: the level in progress is scrolled into view when the window opens.
    useEffect(() => {
        const panorama = panoramaRef.current;
        const pane = panorama?.querySelector<HTMLElement>(`[data-level-index="${currentLevel}"]`);

        if (!panorama || !pane || currentLevel === 0) return;

        panorama.scrollLeft = Math.max(0, pane.offsetLeft - 20);
    }, [track, currentLevel]);

    const renderBegin = () => {
        if (citizenshipEnabled && name !== TALENT_TRACK_CITIZENSHIP) {
            return (
                <NativeTextTree>
                    <div className="octane-talent-begin">
                        <div className="octane-talent-begin__title">{localizeWithFallback(`talent.track.${name}.guide.begin.title`, 'Your journey')}</div>
                        <FormattedText className="octane-talent-begin__text" text={localizeWithFallback(`talent.track.${name}.guide.begin.description`, '')} />
                        <span className="octane-talent-begin__label is-citizenship">{localizeWithFallback(`talent.track.${name}.begin.citizenship`, 'BECOME A CITIZEN')}</span>
                        <img alt="" className="octane-talent-begin__emblem" src={citizenshipAccomplished} />
                        <BaseButton className="octane-habboway__action octane-talent-begin__button" type="button" onClick={() => onOpenTrack(TALENT_TRACK_CITIZENSHIP)}>
                            {localizeWithFallback('talent.track.citizenship.button', 'View the citizenship track')}
                        </BaseButton>
                    </div>
                </NativeTextTree>
            );
        }

        return (
            <NativeTextTree>
                <div className="octane-talent-begin">
                    <div className="octane-talent-begin__title">{localizeWithFallback(`talent.track.${name}.begin.title`, '')}</div>
                    <FormattedText className="octane-talent-begin__text" text={localizeWithFallback(`talent.track.${name}.begin.description`, '')} />
                    <span className="octane-talent-begin__label is-register">{localizeWithFallback(`talent.track.${name}.begin.register`, 'REGISTER AT HABBO')}</span>
                    <div className="octane-talent-begin__avatar">
                        <LayoutAvatarImageView direction={2} figure={figure} />
                    </div>
                    <img alt="" className="octane-talent-begin__check" src={checkMark} />
                </div>
            </NativeTextTree>
        );
    };

    return (
        <NativeTextTree>
            <AirModalDialog
                className="octane-talent-track"
                subtitle={localizeWithFallback(`talent.track.${name}.frame.subtitle`, 'MY TALENT TRACK')}
                title={localizeWithFallback(`talent.track.${name}.frame.title`, '')}
                width={1000}
                onBackdropClick={onClose}
                onClose={onClose}
            >
                <div className="octane-talent-track__viewport">
                    <div ref={panoramaRef} className="octane-talent-track__panorama">
                        {renderBegin()}
                        {track.levels.map((level, index) => (
                            <div key={level.level} className="octane-talent-track__level" data-level-index={index}>
                                <TalentLevelView level={level} trackName={name} onSelectTask={onSelectTask} />
                            </div>
                        ))}
                        <div className="octane-talent-track__end" />
                    </div>
                    <img alt="" className="octane-talent-track__mask is-left" src={maskLeft} />
                    <img alt="" className="octane-talent-track__mask is-right" src={maskRight} />
                </div>
                <div className="octane-talent-progress">
                    <div className="octane-talent-progress__title">
                        {localizeWithFallback(`talent.track.${name}.progress.title`, localizeWithFallback('talent.track.common.progress.title', 'YOUR PROGRESS'))}
                    </div>
                    <div className="octane-talent-progress__bar">
                        <div className="octane-talent-progress__achieved" style={{ width: talentProgressPosition(progress) }} />
                        {track.levels.slice(1).map((level, index) => (
                            <span
                                key={level.level}
                                className={`octane-talent-progress__divider ${(index + 1) / track.levels.length < progress ? 'is-achieved' : ''}`}
                                style={{ left: talentProgressPosition((index + 1) / track.levels.length) }}
                            />
                        ))}
                        <div className="octane-talent-progress__needle" style={{ left: talentProgressPosition(progress) }}>
                            <img alt="" className="octane-talent-progress__glow" src={avatarGlow} />
                            <div className="octane-talent-progress__head">
                                <LayoutAvatarImageView headOnly compactHead compactHeadPadding={0} compactHeadSize={33} direction={2} figure={figure} />
                            </div>
                            <div className="octane-talent-progress__balloon">{localizeWithFallback('talent.track.common.progress.position', 'You are here')}</div>
                        </div>
                    </div>
                </div>
            </AirModalDialog>
        </NativeTextTree>
    );
};
15Sujets
1Dépôts
20Membres
2En ligne
Profile
Inscrit
Dernière visite
Messages
0
Votes positifs
0
Votes négatifs
0

Nous utilisons des cookies pour assurer le bon fonctionnement du site et améliorer votre expérience. en savoir plus

Autoriser