Added an app updated modal when the app has been updated
This commit is contained in:
parent
b0aa0c4c1a
commit
4557479e09
12 changed files with 665 additions and 8 deletions
|
@ -1,9 +1,6 @@
|
|||
import {LaterPromise} from "tc-shared/utils/LaterPromise";
|
||||
import {ErrorCode} from "./ErrorCode";
|
||||
|
||||
/* legacy */
|
||||
export const ErrorID = ErrorCode;
|
||||
|
||||
export class CommandResult {
|
||||
success: boolean;
|
||||
id: number;
|
||||
|
|
|
@ -44,6 +44,9 @@ import "./connection/ConnectionBase";
|
|||
import {ConnectRequestData} from "tc-shared/ipc/ConnectHandler";
|
||||
import "./video-viewer/Controller";
|
||||
|
||||
import "./update/UpdaterWeb";
|
||||
import {checkForUpdatedApp} from "tc-shared/update";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
open_connected_question: () => Promise<boolean>;
|
||||
|
@ -299,7 +302,7 @@ function main() {
|
|||
});
|
||||
|
||||
server_connections.set_active_connection(server_connections.all_connections()[0]);
|
||||
|
||||
checkForUpdatedApp();
|
||||
|
||||
/*
|
||||
(window as any).test_upload = (message?: string) => {
|
||||
|
|
|
@ -347,5 +347,5 @@ loader.register_task(Stage.LOADED, {
|
|||
});
|
||||
},
|
||||
priority: -2
|
||||
})
|
||||
});
|
||||
*/
|
24
shared/js/ui/modal/whats-new/Controller.tsx
Normal file
24
shared/js/ui/modal/whats-new/Controller.tsx
Normal file
|
@ -0,0 +1,24 @@
|
|||
import {spawnReactModal} from "tc-shared/ui/react-elements/Modal";
|
||||
import {InternalModal} from "tc-shared/ui/react-elements/internal-modal/Controller";
|
||||
import * as React from "react";
|
||||
import {WhatsNew} from "tc-shared/ui/modal/whats-new/Renderer";
|
||||
import {Translatable} from "tc-shared/ui/react-elements/i18n";
|
||||
import {ChangeLog} from "tc-shared/update/ChangeLog";
|
||||
|
||||
export function spawnUpdatedModal(changes: { changesUI?: ChangeLog, changesClient?: ChangeLog }) {
|
||||
const modal = spawnReactModal(class extends InternalModal {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
renderBody(): React.ReactElement {
|
||||
return <WhatsNew changesUI={changes.changesUI} changesClient={changes.changesClient} />;
|
||||
}
|
||||
|
||||
title(): string | React.ReactElement<Translatable> {
|
||||
return <Translatable>We've updated the client for you</Translatable>;
|
||||
}
|
||||
});
|
||||
|
||||
modal.show();
|
||||
}
|
268
shared/js/ui/modal/whats-new/Renderer.scss
Normal file
268
shared/js/ui/modal/whats-new/Renderer.scss
Normal file
|
@ -0,0 +1,268 @@
|
|||
@import "../../../../css/static/mixin";
|
||||
@import "../../../../css/static/properties";
|
||||
|
||||
.container {
|
||||
width: 57em;
|
||||
min-width: 20em;
|
||||
|
||||
flex-shrink: 1;
|
||||
flex-grow: 1;
|
||||
|
||||
padding: 1em;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: stretch;
|
||||
|
||||
@include user-select(none);
|
||||
|
||||
.info {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
|
||||
.logo {
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
|
||||
width: 15em;
|
||||
height: 15em;
|
||||
|
||||
display: block;
|
||||
|
||||
margin-top: 1em;
|
||||
margin-right: 2em;
|
||||
|
||||
align-self: center;
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
h1 {
|
||||
all: unset;
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
|
||||
margin-bottom: 0;
|
||||
font-size: 1.8em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
all: unset;
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
|
||||
font-size: 1.5em;
|
||||
margin-bottom: .3em;
|
||||
}
|
||||
|
||||
.subtitleShort {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.subtitleLong {
|
||||
display: block;
|
||||
}
|
||||
|
||||
p {
|
||||
all: unset;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.changes {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: stretch;
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
|
||||
position: relative;
|
||||
|
||||
margin-top: 1.5em;
|
||||
|
||||
padding-bottom: .5em;
|
||||
padding-right: 1em;
|
||||
padding-left: 1em;
|
||||
|
||||
&:after {
|
||||
content: "";
|
||||
display: block;
|
||||
|
||||
height: 2px;
|
||||
|
||||
position: absolute;
|
||||
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
|
||||
@include transition($button_hover_animation_time ease-in-out);
|
||||
}
|
||||
|
||||
.left, .right {
|
||||
font-size: 1.4em;
|
||||
cursor: pointer;
|
||||
|
||||
@include transition($button_hover_animation_time ease-in-out);
|
||||
|
||||
&.hidden {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #b6c4d6;
|
||||
}
|
||||
}
|
||||
|
||||
&.selectedLeft {
|
||||
.left {
|
||||
color: #245184;
|
||||
}
|
||||
|
||||
&:after {
|
||||
background: linear-gradient(to right, #245184 15em, #999999 60%);
|
||||
}
|
||||
}
|
||||
|
||||
&.selectedRight {
|
||||
.right {
|
||||
color: #245184;
|
||||
}
|
||||
|
||||
&:after {
|
||||
background: linear-gradient(to left, #245184 15em, #999999 60%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.body {
|
||||
margin-top: .5em;
|
||||
height: 20em;
|
||||
|
||||
background: #28292b;
|
||||
border-radius: 3px;
|
||||
|
||||
position: relative;
|
||||
|
||||
font-family: consolas, monospace;
|
||||
|
||||
.changeList {
|
||||
flex-shrink: 1;
|
||||
flex-grow: 1;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
|
||||
padding: .4em .8em .8em;
|
||||
|
||||
min-height: 1em;
|
||||
height: 20em;
|
||||
|
||||
overflow-x: hidden;
|
||||
@include chat-scrollbar-vertical();
|
||||
|
||||
@include user-select(text);
|
||||
|
||||
> ul > li {
|
||||
margin-top: 1em;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
all: unset;
|
||||
display: block;
|
||||
|
||||
margin-left: 1em;
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
|
||||
li:before {
|
||||
content: '-';
|
||||
margin-left: -1em;
|
||||
margin-right: .5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
code {
|
||||
background: hsl(0, 0%, 11%);
|
||||
border-radius: 2px;
|
||||
color: hsl(0, 0%, 50%);
|
||||
|
||||
padding: 0 .25em;
|
||||
margin-left: -.25em;
|
||||
margin-right: -.25em;
|
||||
}
|
||||
}
|
||||
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.containerBrowse {
|
||||
position: absolute;
|
||||
|
||||
color: #245184;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
top: 0;
|
||||
right: .5em;
|
||||
|
||||
padding: 1em .5em .5em;
|
||||
border-radius: 3px;
|
||||
|
||||
background-color: #28292bcc;
|
||||
@include transition($button_hover_animation_time ease-in-out);
|
||||
|
||||
&:hover {
|
||||
background-color: #28292b;
|
||||
color: #b6c4d6;
|
||||
}
|
||||
|
||||
a[href]:visited {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (max-width: 40em) {
|
||||
.container .logo {
|
||||
display: none!important;
|
||||
}
|
||||
|
||||
.container .text {
|
||||
.subtitleShort {
|
||||
display: block!important;
|
||||
}
|
||||
|
||||
.subtitleLong {
|
||||
display: none!important;;
|
||||
}
|
||||
|
||||
p br {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
141
shared/js/ui/modal/whats-new/Renderer.tsx
Normal file
141
shared/js/ui/modal/whats-new/Renderer.tsx
Normal file
|
@ -0,0 +1,141 @@
|
|||
import * as React from "react";
|
||||
import * as dompurify from "dompurify";
|
||||
import {useState} from "react";
|
||||
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 = [ changesUI, changesClient ].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>
|
||||
);
|
||||
};
|
|
@ -50,7 +50,8 @@ export class Translatable extends React.Component<{
|
|||
}
|
||||
}
|
||||
|
||||
export const VariadicTranslatable = (props: { text: string, __cacheKey?: string, children?: React.ReactElement[] | React.ReactElement }) => {
|
||||
export type VariadicTranslatableChild = React.ReactElement | string;
|
||||
export const VariadicTranslatable = (props: { text: string, __cacheKey?: string, children?: VariadicTranslatableChild[] | VariadicTranslatableChild }) => {
|
||||
const args = Array.isArray(props.children) ? props.children : [props.children];
|
||||
const argsUseCount = [...new Array(args.length)].map(() => 0);
|
||||
|
||||
|
@ -63,8 +64,13 @@ export const VariadicTranslatable = (props: { text: string, __cacheKey?: string,
|
|||
return e;
|
||||
|
||||
let element = args[e];
|
||||
if(argsUseCount[e])
|
||||
element = cloneElement(element);
|
||||
if(argsUseCount[e]) {
|
||||
if(typeof element === "string") {
|
||||
/* do nothing */
|
||||
} else {
|
||||
element = cloneElement(element);
|
||||
}
|
||||
}
|
||||
argsUseCount[e]++;
|
||||
|
||||
return <React.Fragment key={"argument-" + e + "-" + argsUseCount[e]}>{element}</React.Fragment>;
|
||||
|
|
15
shared/js/update/ChangeLog.ts
Normal file
15
shared/js/update/ChangeLog.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
export type ChangeSetEntry = ChangeSet | string;
|
||||
|
||||
export interface ChangeSet {
|
||||
title?: string;
|
||||
changes: ChangeSetEntry[];
|
||||
}
|
||||
|
||||
export interface ChangeLogEntry extends ChangeSet {
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export interface ChangeLog {
|
||||
changes: ChangeLogEntry[],
|
||||
currentVersion: string
|
||||
}
|
11
shared/js/update/Updater.ts
Normal file
11
shared/js/update/Updater.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import {ChangeLog} from "tc-shared/update/ChangeLog";
|
||||
|
||||
export interface Updater {
|
||||
getChangeLog() : ChangeLog;
|
||||
|
||||
getLastUsedVersion() : string;
|
||||
getCurrentVersion() : string;
|
||||
|
||||
/* update the last used version to the current version */
|
||||
updateUsedVersion();
|
||||
}
|
130
shared/js/update/UpdaterWeb.ts
Normal file
130
shared/js/update/UpdaterWeb.ts
Normal file
|
@ -0,0 +1,130 @@
|
|||
import {ChangeLog, ChangeSetEntry} from "tc-shared/update/ChangeLog";
|
||||
import * as loader from "tc-loader";
|
||||
import {Stage} from "tc-loader";
|
||||
import {setUIUpdater} from "tc-shared/update/index";
|
||||
import {Updater} from "tc-shared/update/Updater";
|
||||
|
||||
const ChangeLogContents: string = require("../../../ChangeLog.md");
|
||||
const EntryRegex = /^\* \*\*([0-9]{2})\.([0-9]{2})\.([0-9]{2})\*\*$/m;
|
||||
|
||||
function parseChangeLogEntry(lines: string[], index: number) : { entries: ChangeSetEntry[], index: number } {
|
||||
const entryDepth = lines[index].indexOf("-");
|
||||
if(entryDepth === -1) {
|
||||
throw "missing entry depth for line " + index;
|
||||
}
|
||||
|
||||
let entries = [] as ChangeSetEntry[];
|
||||
let currentEntry;
|
||||
while(index < lines.length && !lines[index].match(EntryRegex)) {
|
||||
let trimmed = lines[index].trim();
|
||||
if(trimmed.length === 0) {
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(trimmed[0] === '-') {
|
||||
const depth = lines[index].indexOf('-');
|
||||
if(depth > entryDepth) {
|
||||
if(typeof currentEntry === "undefined")
|
||||
throw "missing change child entries parent at line " + index;
|
||||
|
||||
const result = parseChangeLogEntry(lines, index);
|
||||
entries.push({
|
||||
changes: result.entries,
|
||||
title: currentEntry
|
||||
});
|
||||
index = result.index;
|
||||
} else if(depth < entryDepth) {
|
||||
/* we're done with our block */
|
||||
break;
|
||||
} else {
|
||||
/* new entry */
|
||||
if(typeof currentEntry === "string")
|
||||
entries.push(currentEntry);
|
||||
|
||||
currentEntry = trimmed.substr(1).trim();
|
||||
}
|
||||
} else {
|
||||
if(typeof currentEntry === "undefined")
|
||||
throw "this should never happen!";
|
||||
|
||||
currentEntry += "\n" + trimmed;
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
if(typeof currentEntry === "string")
|
||||
entries.push(currentEntry);
|
||||
|
||||
return {
|
||||
index: index,
|
||||
entries: entries
|
||||
};
|
||||
}
|
||||
|
||||
function parseUIChangeLog() : ChangeLog {
|
||||
let result: ChangeLog = {
|
||||
currentVersion: "unknown",
|
||||
changes: []
|
||||
}
|
||||
|
||||
const lines = ChangeLogContents.split("\n");
|
||||
let index = 0;
|
||||
|
||||
while(index < lines.length && !lines[index].match(EntryRegex))
|
||||
index++;
|
||||
|
||||
while(index < lines.length) {
|
||||
const [ _, day, month, year ] = lines[index].match(EntryRegex);
|
||||
|
||||
const entry = parseChangeLogEntry(lines, index + 1);
|
||||
result.changes.push({
|
||||
timestamp: day + "." + month + "." + year,
|
||||
changes: entry.entries
|
||||
});
|
||||
|
||||
index = entry.index;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const kLastUsedVersionKey = "updater-used-version-web";
|
||||
class WebUpdater implements Updater {
|
||||
private readonly changeLog: ChangeLog;
|
||||
private readonly currentVersion: string;
|
||||
|
||||
constructor() {
|
||||
this.changeLog = parseUIChangeLog();
|
||||
|
||||
const currentBuildTimestamp = new Date(__build.timestamp * 1000);
|
||||
this.currentVersion = ("00" + currentBuildTimestamp.getUTCDate()).substr(-2) + "." +
|
||||
("00" + currentBuildTimestamp.getUTCMonth()).substr(-2) + "." +
|
||||
currentBuildTimestamp.getUTCFullYear().toString().substr(2);
|
||||
}
|
||||
|
||||
getChangeLog(): ChangeLog {
|
||||
return this.changeLog;
|
||||
}
|
||||
|
||||
getCurrentVersion(): string {
|
||||
return this.currentVersion;
|
||||
}
|
||||
|
||||
getLastUsedVersion(): string {
|
||||
return localStorage.getItem(kLastUsedVersionKey) || "08.08.20";
|
||||
}
|
||||
|
||||
updateUsedVersion() {
|
||||
localStorage.setItem(kLastUsedVersionKey, this.getCurrentVersion());
|
||||
}
|
||||
}
|
||||
|
||||
loader.register_task(Stage.JAVASCRIPT_INITIALIZING, {
|
||||
name: "web updater init",
|
||||
function: async () => {
|
||||
setUIUpdater(new WebUpdater());
|
||||
},
|
||||
priority: 50
|
||||
});
|
55
shared/js/update/index.ts
Normal file
55
shared/js/update/index.ts
Normal file
|
@ -0,0 +1,55 @@
|
|||
import {Updater} from "./Updater";
|
||||
import {ChangeLog} from "tc-shared/update/ChangeLog";
|
||||
import {spawnUpdatedModal} from "tc-shared/ui/modal/whats-new/Controller";
|
||||
|
||||
let updaterUi: Updater;
|
||||
let updaterNative: Updater;
|
||||
|
||||
export function setUIUpdater(updater: Updater) {
|
||||
if(typeof updaterUi !== "undefined") {
|
||||
throw tr("An UI updater has already been registered");
|
||||
}
|
||||
updaterUi = updater;
|
||||
}
|
||||
|
||||
export function setNativeUpdater(updater: Updater) {
|
||||
if(typeof updaterNative !== "undefined") {
|
||||
throw tr("An native updater has already been registered");
|
||||
}
|
||||
updaterNative = updater;
|
||||
}
|
||||
|
||||
function getChangedChangeLog(updater: Updater) : ChangeLog | undefined {
|
||||
if(updater.getCurrentVersion() === updater.getLastUsedVersion())
|
||||
return undefined;
|
||||
|
||||
let changes = {
|
||||
changes: [],
|
||||
currentVersion: updater.getCurrentVersion()
|
||||
} as ChangeLog;
|
||||
|
||||
let usedVersion = updater.getLastUsedVersion();
|
||||
for(const change of updater.getChangeLog().changes) {
|
||||
if(change.timestamp === usedVersion)
|
||||
break;
|
||||
|
||||
changes.changes.push(change);
|
||||
}
|
||||
|
||||
return changes.changes.length > 0 ? changes : undefined;
|
||||
}
|
||||
|
||||
export function checkForUpdatedApp() {
|
||||
let changesUI = updaterUi ? getChangedChangeLog(updaterUi) : undefined;
|
||||
let changesNative = updaterNative ? getChangedChangeLog(updaterNative) : undefined;
|
||||
|
||||
if(changesUI !== undefined || changesNative !== undefined) {
|
||||
spawnUpdatedModal({
|
||||
changesUI: changesUI,
|
||||
changesClient: changesNative
|
||||
});
|
||||
|
||||
updaterUi?.updateUsedVersion();
|
||||
updaterNative?.updateUsedVersion();
|
||||
}
|
||||
}
|
|
@ -226,6 +226,13 @@ export const config = async (target: "web" | "client"): Promise<Configuration> =
|
|||
{
|
||||
test: /\.svg$/,
|
||||
loader: 'svg-inline-loader'
|
||||
},
|
||||
{
|
||||
test: /ChangeLog\.md$/i,
|
||||
loader: "raw-loader",
|
||||
options: {
|
||||
esModule: false
|
||||
}
|
||||
}
|
||||
],
|
||||
},
|
||||
|
|
Loading…
Add table
Reference in a new issue