2016-10-12 02:55:53 +02:00
|
|
|
/*
|
|
|
|
This file is part of TALER
|
|
|
|
(C) 2016 Inria
|
|
|
|
|
|
|
|
TALER is free software; you can redistribute it and/or modify it under the
|
|
|
|
terms of the GNU General Public License as published by the Free Software
|
|
|
|
Foundation; either version 3, or (at your option) any later version.
|
|
|
|
|
|
|
|
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
|
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
|
|
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License along with
|
|
|
|
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Show contents of the wallet as a tree.
|
|
|
|
*
|
|
|
|
* @author Florian Dold
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
2017-06-04 17:56:55 +02:00
|
|
|
import { getTalerStampDate } from "../../helpers";
|
2017-04-20 03:09:25 +02:00
|
|
|
import {
|
|
|
|
CoinRecord,
|
2017-05-28 23:15:41 +02:00
|
|
|
CoinStatus,
|
|
|
|
DenominationRecord,
|
|
|
|
ExchangeRecord,
|
|
|
|
PreCoinRecord,
|
|
|
|
ReserveRecord,
|
|
|
|
} from "../../types";
|
|
|
|
|
2017-04-20 03:09:25 +02:00
|
|
|
import { ImplicitStateComponent, StateHolder } from "../components";
|
2016-10-13 02:36:33 +02:00
|
|
|
import {
|
2017-05-29 15:18:48 +02:00
|
|
|
getCoins,
|
|
|
|
getDenoms,
|
|
|
|
getExchanges,
|
|
|
|
getPreCoins,
|
|
|
|
getReserves,
|
|
|
|
payback,
|
|
|
|
refresh,
|
2017-04-20 03:09:25 +02:00
|
|
|
} from "../wxApi";
|
2017-05-29 15:18:48 +02:00
|
|
|
|
2017-06-04 17:56:55 +02:00
|
|
|
import { renderAmount } from "../renderHtml";
|
|
|
|
|
2017-04-20 03:09:25 +02:00
|
|
|
import * as React from "react";
|
|
|
|
import * as ReactDOM from "react-dom";
|
2016-10-12 02:55:53 +02:00
|
|
|
|
|
|
|
interface ReserveViewProps {
|
2016-10-13 02:23:24 +02:00
|
|
|
reserve: ReserveRecord;
|
2016-10-12 02:55:53 +02:00
|
|
|
}
|
|
|
|
|
2017-08-14 04:59:43 +02:00
|
|
|
class ReserveView extends React.Component<ReserveViewProps, {}> {
|
2016-10-12 02:55:53 +02:00
|
|
|
render(): JSX.Element {
|
2017-05-29 15:18:48 +02:00
|
|
|
const r: ReserveRecord = this.props.reserve;
|
2016-10-12 02:55:53 +02:00
|
|
|
return (
|
|
|
|
<div className="tree-item">
|
|
|
|
<ul>
|
|
|
|
<li>Key: {r.reserve_pub}</li>
|
|
|
|
<li>Created: {(new Date(r.created * 1000).toString())}</li>
|
2017-06-04 17:56:55 +02:00
|
|
|
<li>Current: {r.current_amount ? renderAmount(r.current_amount!) : "null"}</li>
|
|
|
|
<li>Requested: {renderAmount(r.requested_amount)}</li>
|
2017-10-15 18:30:02 +02:00
|
|
|
<li>Confirmed: {r.timestamp_confirmed}</li>
|
2016-10-12 02:55:53 +02:00
|
|
|
</ul>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
interface ReserveListProps {
|
|
|
|
exchangeBaseUrl: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface ToggleProps {
|
|
|
|
expanded: StateHolder<boolean>;
|
|
|
|
}
|
|
|
|
|
|
|
|
class Toggle extends ImplicitStateComponent<ToggleProps> {
|
|
|
|
renderButton() {
|
2017-05-29 15:18:48 +02:00
|
|
|
const show = () => {
|
2016-10-12 02:55:53 +02:00
|
|
|
this.props.expanded(true);
|
|
|
|
this.setState({});
|
|
|
|
};
|
2017-05-29 15:18:48 +02:00
|
|
|
const hide = () => {
|
2016-10-12 02:55:53 +02:00
|
|
|
this.props.expanded(false);
|
|
|
|
this.setState({});
|
|
|
|
};
|
|
|
|
if (this.props.expanded()) {
|
|
|
|
return <button onClick={hide}>hide</button>;
|
|
|
|
}
|
|
|
|
return <button onClick={show}>show</button>;
|
|
|
|
|
|
|
|
}
|
|
|
|
render() {
|
|
|
|
return (
|
2016-11-13 08:16:12 +01:00
|
|
|
<div style={{display: "inline"}}>
|
2016-10-12 02:55:53 +02:00
|
|
|
{this.renderButton()}
|
|
|
|
{this.props.expanded() ? this.props.children : []}
|
|
|
|
</div>);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface CoinViewProps {
|
2016-11-15 15:07:17 +01:00
|
|
|
coin: CoinRecord;
|
2016-10-12 02:55:53 +02:00
|
|
|
}
|
|
|
|
|
2016-10-12 23:30:10 +02:00
|
|
|
interface RefreshDialogProps {
|
2016-11-15 15:07:17 +01:00
|
|
|
coin: CoinRecord;
|
2016-10-12 23:30:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
class RefreshDialog extends ImplicitStateComponent<RefreshDialogProps> {
|
2017-05-29 15:18:48 +02:00
|
|
|
private refreshRequested = this.makeState<boolean>(false);
|
2016-10-12 23:30:10 +02:00
|
|
|
render(): JSX.Element {
|
|
|
|
if (!this.refreshRequested()) {
|
|
|
|
return (
|
2016-11-13 08:16:12 +01:00
|
|
|
<div style={{display: "inline"}}>
|
2016-10-12 23:30:10 +02:00
|
|
|
<button onClick={() => this.refreshRequested(true)}>refresh</button>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return (
|
|
|
|
<div>
|
|
|
|
Refresh amount: <input type="text" size={10} />
|
2016-10-13 02:36:33 +02:00
|
|
|
<button onClick={() => refresh(this.props.coin.coinPub)}>ok</button>
|
2016-10-12 23:30:10 +02:00
|
|
|
<button onClick={() => this.refreshRequested(false)}>cancel</button>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-14 04:59:43 +02:00
|
|
|
class CoinView extends React.Component<CoinViewProps, {}> {
|
2016-10-12 02:55:53 +02:00
|
|
|
render() {
|
2017-05-29 15:18:48 +02:00
|
|
|
const c = this.props.coin;
|
2016-10-12 02:55:53 +02:00
|
|
|
return (
|
|
|
|
<div className="tree-item">
|
|
|
|
<ul>
|
|
|
|
<li>Key: {c.coinPub}</li>
|
2017-06-04 17:56:55 +02:00
|
|
|
<li>Current amount: {renderAmount(c.currentAmount)}</li>
|
2016-11-19 15:59:17 +01:00
|
|
|
<li>Denomination: <ExpanderText text={c.denomPub} /></li>
|
2016-10-12 02:55:53 +02:00
|
|
|
<li>Suspended: {(c.suspended || false).toString()}</li>
|
2017-04-13 16:08:41 +02:00
|
|
|
<li>Status: {CoinStatus[c.status]}</li>
|
2016-10-12 23:30:10 +02:00
|
|
|
<li><RefreshDialog coin={c} /></li>
|
2017-05-01 04:33:47 +02:00
|
|
|
<li><button onClick={() => payback(c.coinPub)}>Payback</button></li>
|
2016-10-12 02:55:53 +02:00
|
|
|
</ul>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface PreCoinViewProps {
|
2016-11-15 15:07:17 +01:00
|
|
|
precoin: PreCoinRecord;
|
2016-10-12 02:55:53 +02:00
|
|
|
}
|
|
|
|
|
2017-08-14 04:59:43 +02:00
|
|
|
class PreCoinView extends React.Component<PreCoinViewProps, {}> {
|
2016-10-12 02:55:53 +02:00
|
|
|
render() {
|
2017-05-29 15:18:48 +02:00
|
|
|
const c = this.props.precoin;
|
2016-10-12 02:55:53 +02:00
|
|
|
return (
|
|
|
|
<div className="tree-item">
|
|
|
|
<ul>
|
|
|
|
<li>Key: {c.coinPub}</li>
|
|
|
|
</ul>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
interface CoinListProps {
|
|
|
|
exchangeBaseUrl: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
class CoinList extends ImplicitStateComponent<CoinListProps> {
|
2017-05-29 15:18:48 +02:00
|
|
|
private coins = this.makeState<CoinRecord[] | null>(null);
|
|
|
|
private expanded = this.makeState<boolean>(false);
|
2016-10-12 02:55:53 +02:00
|
|
|
|
|
|
|
constructor(props: CoinListProps) {
|
|
|
|
super(props);
|
2016-11-13 08:16:12 +01:00
|
|
|
this.update(props);
|
2016-10-12 02:55:53 +02:00
|
|
|
}
|
|
|
|
|
2016-11-13 08:16:12 +01:00
|
|
|
async update(props: CoinListProps) {
|
2017-05-29 15:18:48 +02:00
|
|
|
const coins = await getCoins(props.exchangeBaseUrl);
|
2016-10-12 02:55:53 +02:00
|
|
|
this.coins(coins);
|
|
|
|
}
|
|
|
|
|
2016-11-13 08:16:12 +01:00
|
|
|
componentWillReceiveProps(newProps: CoinListProps) {
|
|
|
|
this.update(newProps);
|
|
|
|
}
|
|
|
|
|
2016-10-12 02:55:53 +02:00
|
|
|
render(): JSX.Element {
|
|
|
|
if (!this.coins()) {
|
|
|
|
return <div>...</div>;
|
|
|
|
}
|
|
|
|
return (
|
|
|
|
<div className="tree-item">
|
|
|
|
Coins ({this.coins() !.length.toString()})
|
|
|
|
{" "}
|
|
|
|
<Toggle expanded={this.expanded}>
|
|
|
|
{this.coins() !.map((c) => <CoinView coin={c} />)}
|
|
|
|
</Toggle>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface PreCoinListProps {
|
|
|
|
exchangeBaseUrl: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
class PreCoinList extends ImplicitStateComponent<PreCoinListProps> {
|
2017-05-29 15:18:48 +02:00
|
|
|
private precoins = this.makeState<PreCoinRecord[] | null>(null);
|
|
|
|
private expanded = this.makeState<boolean>(false);
|
2016-10-12 02:55:53 +02:00
|
|
|
|
|
|
|
constructor(props: PreCoinListProps) {
|
|
|
|
super(props);
|
|
|
|
this.update();
|
|
|
|
}
|
|
|
|
|
|
|
|
async update() {
|
2017-05-29 15:18:48 +02:00
|
|
|
const precoins = await getPreCoins(this.props.exchangeBaseUrl);
|
2016-10-12 02:55:53 +02:00
|
|
|
this.precoins(precoins);
|
|
|
|
}
|
|
|
|
|
|
|
|
render(): JSX.Element {
|
|
|
|
if (!this.precoins()) {
|
|
|
|
return <div>...</div>;
|
|
|
|
}
|
|
|
|
return (
|
|
|
|
<div className="tree-item">
|
2016-11-19 15:59:17 +01:00
|
|
|
Planchets ({this.precoins() !.length.toString()})
|
2016-10-12 02:55:53 +02:00
|
|
|
{" "}
|
|
|
|
<Toggle expanded={this.expanded}>
|
|
|
|
{this.precoins() !.map((c) => <PreCoinView precoin={c} />)}
|
|
|
|
</Toggle>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
interface DenominationListProps {
|
2016-11-15 15:07:17 +01:00
|
|
|
exchange: ExchangeRecord;
|
2016-10-12 02:55:53 +02:00
|
|
|
}
|
|
|
|
|
2016-11-13 08:16:12 +01:00
|
|
|
interface ExpanderTextProps {
|
|
|
|
text: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
class ExpanderText extends ImplicitStateComponent<ExpanderTextProps> {
|
2017-05-29 15:18:48 +02:00
|
|
|
private expanded = this.makeState<boolean>(false);
|
|
|
|
private textArea: any = undefined;
|
2016-11-13 08:16:12 +01:00
|
|
|
|
|
|
|
componentDidUpdate() {
|
|
|
|
if (this.expanded() && this.textArea) {
|
|
|
|
this.textArea.focus();
|
|
|
|
this.textArea.scrollTop = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
render(): JSX.Element {
|
|
|
|
if (!this.expanded()) {
|
|
|
|
return (
|
|
|
|
<span onClick={() => { this.expanded(true); }}>
|
|
|
|
{(this.props.text.length <= 10)
|
2017-05-29 15:18:48 +02:00
|
|
|
? this.props.text
|
2016-11-13 08:16:12 +01:00
|
|
|
: (
|
|
|
|
<span>
|
2017-05-29 15:18:48 +02:00
|
|
|
{this.props.text.substring(0, 10)}
|
2016-11-13 08:16:12 +01:00
|
|
|
<span style={{textDecoration: "underline"}}>...</span>
|
|
|
|
</span>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
</span>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return (
|
|
|
|
<textarea
|
|
|
|
readOnly
|
|
|
|
style={{display: "block"}}
|
|
|
|
onBlur={() => this.expanded(false)}
|
|
|
|
ref={(e) => this.textArea = e}>
|
|
|
|
{this.props.text}
|
|
|
|
</textarea>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-12 02:55:53 +02:00
|
|
|
class DenominationList extends ImplicitStateComponent<DenominationListProps> {
|
2017-05-29 15:18:48 +02:00
|
|
|
private expanded = this.makeState<boolean>(false);
|
|
|
|
private denoms = this.makeState<undefined|DenominationRecord[]>(undefined);
|
2016-10-12 02:55:53 +02:00
|
|
|
|
2016-11-16 01:59:39 +01:00
|
|
|
constructor(props: DenominationListProps) {
|
|
|
|
super(props);
|
|
|
|
this.update();
|
|
|
|
}
|
|
|
|
|
|
|
|
async update() {
|
2017-05-29 15:18:48 +02:00
|
|
|
const d = await getDenoms(this.props.exchange.baseUrl);
|
2016-11-16 01:59:39 +01:00
|
|
|
this.denoms(d);
|
|
|
|
}
|
|
|
|
|
|
|
|
renderDenom(d: DenominationRecord) {
|
2016-10-12 02:55:53 +02:00
|
|
|
return (
|
|
|
|
<div className="tree-item">
|
|
|
|
<ul>
|
2016-11-19 21:31:36 +01:00
|
|
|
<li>Offered: {d.isOffered ? "yes" : "no"}</li>
|
2017-06-04 17:56:55 +02:00
|
|
|
<li>Value: {renderAmount(d.value)}</li>
|
|
|
|
<li>Withdraw fee: {renderAmount(d.feeWithdraw)}</li>
|
|
|
|
<li>Refresh fee: {renderAmount(d.feeRefresh)}</li>
|
|
|
|
<li>Deposit fee: {renderAmount(d.feeDeposit)}</li>
|
|
|
|
<li>Refund fee: {renderAmount(d.feeRefund)}</li>
|
2016-11-16 01:59:39 +01:00
|
|
|
<li>Start: {getTalerStampDate(d.stampStart)!.toString()}</li>
|
|
|
|
<li>Withdraw expiration: {getTalerStampDate(d.stampExpireWithdraw)!.toString()}</li>
|
|
|
|
<li>Legal expiration: {getTalerStampDate(d.stampExpireLegal)!.toString()}</li>
|
|
|
|
<li>Deposit expiration: {getTalerStampDate(d.stampExpireDeposit)!.toString()}</li>
|
|
|
|
<li>Denom pub: <ExpanderText text={d.denomPub} /></li>
|
2016-10-12 02:55:53 +02:00
|
|
|
</ul>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
render(): JSX.Element {
|
2017-05-29 15:18:48 +02:00
|
|
|
const denoms = this.denoms();
|
2016-11-16 01:59:39 +01:00
|
|
|
if (!denoms) {
|
|
|
|
return (
|
|
|
|
<div className="tree-item">
|
|
|
|
Denominations (...)
|
|
|
|
{" "}
|
|
|
|
<Toggle expanded={this.expanded}>
|
|
|
|
...
|
|
|
|
</Toggle>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
2016-10-12 02:55:53 +02:00
|
|
|
return (
|
|
|
|
<div className="tree-item">
|
2016-11-16 01:59:39 +01:00
|
|
|
Denominations ({denoms.length.toString()})
|
2016-10-12 02:55:53 +02:00
|
|
|
{" "}
|
|
|
|
<Toggle expanded={this.expanded}>
|
2016-11-16 01:59:39 +01:00
|
|
|
{denoms.map((d) => this.renderDenom(d))}
|
2016-10-12 02:55:53 +02:00
|
|
|
</Toggle>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-29 15:18:48 +02:00
|
|
|
|
2016-10-12 02:55:53 +02:00
|
|
|
class ReserveList extends ImplicitStateComponent<ReserveListProps> {
|
2017-05-29 15:18:48 +02:00
|
|
|
private reserves = this.makeState<ReserveRecord[] | null>(null);
|
|
|
|
private expanded = this.makeState<boolean>(false);
|
2016-10-12 02:55:53 +02:00
|
|
|
|
|
|
|
constructor(props: ReserveListProps) {
|
|
|
|
super(props);
|
|
|
|
this.update();
|
|
|
|
}
|
|
|
|
|
|
|
|
async update() {
|
2017-05-29 15:18:48 +02:00
|
|
|
const reserves = await getReserves(this.props.exchangeBaseUrl);
|
2016-10-12 02:55:53 +02:00
|
|
|
this.reserves(reserves);
|
|
|
|
}
|
|
|
|
|
|
|
|
render(): JSX.Element {
|
|
|
|
if (!this.reserves()) {
|
|
|
|
return <div>...</div>;
|
|
|
|
}
|
|
|
|
return (
|
|
|
|
<div className="tree-item">
|
|
|
|
Reserves ({this.reserves() !.length.toString()})
|
|
|
|
{" "}
|
|
|
|
<Toggle expanded={this.expanded}>
|
|
|
|
{this.reserves() !.map((r) => <ReserveView reserve={r} />)}
|
|
|
|
</Toggle>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
interface ExchangeProps {
|
2016-11-15 15:07:17 +01:00
|
|
|
exchange: ExchangeRecord;
|
2016-10-12 02:55:53 +02:00
|
|
|
}
|
|
|
|
|
2017-08-14 04:59:43 +02:00
|
|
|
class ExchangeView extends React.Component<ExchangeProps, {}> {
|
2016-10-12 02:55:53 +02:00
|
|
|
render(): JSX.Element {
|
2017-05-29 15:18:48 +02:00
|
|
|
const e = this.props.exchange;
|
2016-10-12 02:55:53 +02:00
|
|
|
return (
|
|
|
|
<div className="tree-item">
|
2016-11-13 08:16:12 +01:00
|
|
|
<ul>
|
|
|
|
<li>Exchange Base Url: {this.props.exchange.baseUrl}</li>
|
|
|
|
<li>Master public key: <ExpanderText text={this.props.exchange.masterPublicKey} /></li>
|
|
|
|
</ul>
|
2016-10-12 02:55:53 +02:00
|
|
|
<DenominationList exchange={e} />
|
|
|
|
<ReserveList exchangeBaseUrl={this.props.exchange.baseUrl} />
|
|
|
|
<CoinList exchangeBaseUrl={this.props.exchange.baseUrl} />
|
|
|
|
<PreCoinList exchangeBaseUrl={this.props.exchange.baseUrl} />
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
interface ExchangesListState {
|
2016-11-15 15:07:17 +01:00
|
|
|
exchanges?: ExchangeRecord[];
|
2016-10-12 02:55:53 +02:00
|
|
|
}
|
|
|
|
|
2016-11-13 08:16:12 +01:00
|
|
|
class ExchangesList extends React.Component<any, ExchangesListState> {
|
2016-10-12 02:55:53 +02:00
|
|
|
constructor() {
|
|
|
|
super();
|
2017-05-29 15:18:48 +02:00
|
|
|
const port = chrome.runtime.connect();
|
2016-11-13 08:16:12 +01:00
|
|
|
port.onMessage.addListener((msg: any) => {
|
|
|
|
if (msg.notify) {
|
|
|
|
console.log("got notified");
|
|
|
|
this.update();
|
|
|
|
}
|
|
|
|
});
|
2016-10-12 02:55:53 +02:00
|
|
|
this.update();
|
2016-11-13 10:17:39 +01:00
|
|
|
this.state = {} as any;
|
2016-10-12 02:55:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
async update() {
|
2017-05-29 15:18:48 +02:00
|
|
|
const exchanges = await getExchanges();
|
2016-10-12 02:55:53 +02:00
|
|
|
console.log("exchanges: ", exchanges);
|
|
|
|
this.setState({ exchanges });
|
|
|
|
}
|
|
|
|
|
|
|
|
render(): JSX.Element {
|
2017-05-29 15:18:48 +02:00
|
|
|
const exchanges = this.state.exchanges;
|
2016-11-13 10:17:39 +01:00
|
|
|
if (!exchanges) {
|
2016-10-12 02:55:53 +02:00
|
|
|
return <span>...</span>;
|
|
|
|
}
|
|
|
|
return (
|
|
|
|
<div className="tree-item">
|
2016-11-13 10:17:39 +01:00
|
|
|
Exchanges ({exchanges.length.toString()}):
|
2017-05-29 15:18:48 +02:00
|
|
|
{exchanges.map((e) => <ExchangeView exchange={e} />)}
|
2016-10-12 02:55:53 +02:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-29 15:18:48 +02:00
|
|
|
function main() {
|
2016-11-13 10:17:39 +01:00
|
|
|
ReactDOM.render(<ExchangesList />, document.getElementById("container")!);
|
2016-10-12 02:55:53 +02:00
|
|
|
}
|
2017-04-20 03:09:25 +02:00
|
|
|
|
|
|
|
document.addEventListener("DOMContentLoaded", main);
|