import * as React from "react"; import {useState} from "react"; import * as dompurify from "dompurify"; import {ChangeLog, ChangeLogEntry, ChangeSet} from "tc-shared/update/ChangeLog"; import {Translatable, VariadicTranslatable} from "tc-shared/ui/react-elements/i18n"; import {guid} from "tc-shared/crypto/uid"; const {Remarkable} = require("remarkable"); const cssStyle = require("./Renderer.scss"); const mdRenderer = new Remarkable(); const brElementUuid = guid(); export interface DisplayableChangeList extends ChangeLog { title: React.ReactElement<Translatable>; url: string; } mdRenderer.renderer.rules.link_open = function (tokens, idx, options /*, env */) { const title = tokens[idx].title ? (`title="${tokens[idx].title}"`) : ''; const target = options.linkTarget ? (`target="${options.linkTarget}"`) : ''; let href = `<a href="${tokens[idx].href}" ${title} ${target} ></a>`; href = dompurify.sanitize(href); if (href.substr(-4) !== "</a>") { return "<-- invalid link open... -->"; } return href.substr(0, href.length - 4); }; const ChangeSetRenderer = (props: { set: ChangeSet }) => { return ( <React.Fragment> {props.set.title ? <li key={"title"}>{props.set.title}</li> : undefined} <ul> {props.set.changes.map((change, index) => typeof change === "string" ? <li key={index} dangerouslySetInnerHTML={{ __html: mdRenderer.renderInline(change.replace(/\n/g, brElementUuid)).replace(new RegExp(brElementUuid, "g"), "<br />") }}/> : <ChangeSetRenderer set={change} key={index}/>)} </ul> </React.Fragment> ); }; const ChangeLogEntryRenderer = React.memo((props: { entry: ChangeLogEntry }) => ( <li> <b>{props.entry.timestamp}</b> <ChangeSetRenderer set={props.entry}/> </li> )); const DisplayableChangeListRenderer = (props: { list: DisplayableChangeList | undefined, visible: boolean }) => ( <div className={cssStyle.body + " " + (!props.visible ? cssStyle.hidden : "")}> <div className={cssStyle.changeList}> <ul> {props.list?.changes.map((value, index) => <ChangeLogEntryRenderer entry={value} key={index}/>)} </ul> </div> <div className={cssStyle.containerBrowse}> <a href={props.list?.url} target={"_blank"}><Translatable>Open full Change Log</Translatable></a> </div> </div> ); const ChangeListRenderer = (props: { left?: DisplayableChangeList, right?: DisplayableChangeList, defaultSelected: "right" | "left" | "none" }) => { const [selected, setSelected] = useState<"left" | "right" | "none">(props.defaultSelected); return ( <div className={cssStyle.changes}> <div className={cssStyle.header + " " + (selected === "left" ? cssStyle.selectedLeft : selected === "right" ? cssStyle.selectedRight : "")}> <div className={cssStyle.left + " " + (props.left ? "" : cssStyle.hidden)} onClick={() => setSelected("left")}> <a>{props.left?.title}</a> </div> <div className={cssStyle.right + " " + (props.right ? "" : cssStyle.hidden)} onClick={() => setSelected("right")}> <a>{props.right?.title}</a> </div> </div> <DisplayableChangeListRenderer list={props.left} visible={selected === "left"}/> <DisplayableChangeListRenderer list={props.right} visible={selected === "right"}/> </div> ) }; export const WhatsNew = (props: { changesUI?: ChangeLog, changesClient?: ChangeLog }) => { let subtitleLong, infoText; let changesUI = props.changesUI ? Object.assign({ title: <Translatable>UI Change Log</Translatable>, url: "https://github.com/TeaSpeak/TeaWeb/blob/master/ChangeLog.md" }, props.changesUI) : undefined; let changesClient = props.changesClient ? Object.assign({ title: <Translatable>Client Change Log</Translatable>, url: "https://github.com/TeaSpeak/TeaClient/blob/master/ChangeLog.txt" }, props.changesClient) : undefined; let versionUIDate = props.changesUI?.currentVersion, versionNativeDate = props.changesClient?.currentVersion; if (__build.target === "web") { subtitleLong = ( <Translatable key={"sub-web"}>We've successfully updated the web client for you.</Translatable> ); infoText = ( <VariadicTranslatable key={"info-web"} text={"The web client has been updated to the version from {}."} > {versionUIDate} </VariadicTranslatable> ); } else if (props.changesUI && props.changesClient) { subtitleLong = ( <Translatable key={"sub-native-client-ui"} > We've successfully updated the native client and its UI for you. </Translatable> ); infoText = ( <React.Fragment key={"info-native-client-ui"}> <VariadicTranslatable text={"The native client has been updated to the version from {}."}>{versionNativeDate}</VariadicTranslatable> <VariadicTranslatable text={"Its UI has been updated to the version {}."}>{versionUIDate}</VariadicTranslatable> </React.Fragment> ); } else if (props.changesClient) { subtitleLong = ( <Translatable key={"sub-native-client"}> We've successfully updated the native client for you. </Translatable> ); infoText = ( <VariadicTranslatable key={"info-native-client"} text={"The native client has been updated to the version {}."}> {versionNativeDate} </VariadicTranslatable> ); } else if (props.changesUI) { subtitleLong = ( <Translatable key={"sub-native-ui"} > We've successfully updated the native clients UI for you. </Translatable> ); infoText = ( <VariadicTranslatable key={"info-native-ui"} text={"The native clients UI has been updated to the version from 18.08.2020."} > {versionUIDate} </VariadicTranslatable> ); } const changes = [changesClient, changesUI].filter(e => !!e); return ( <div className={cssStyle.container}> <div className={cssStyle.info}> <div className={cssStyle.logo}> <img alt={tr("TeaSpeak logo")} src="img/teaspeak_cup_animated.png"/> </div> <div className={cssStyle.text}> <h1><Translatable>Welcome back!</Translatable></h1> <h2 className={cssStyle.subtitleLong}>{subtitleLong}</h2> <h2 className={cssStyle.subtitleShort}><Translatable>The client has been updated.</Translatable> </h2> <p> <Translatable>While you've been away, resting, we did some work.</Translatable> <br/> {infoText} <br/> <Translatable>A list of changes, bugfixes and new features can be found bellow.</Translatable> </p> <a><Translatable>Enjoy!</Translatable></a> </div> </div> <ChangeListRenderer defaultSelected={"right"} right={changes[0]} left={changes[1]} /> </div> ); };