You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
527 lines
23 KiB
527 lines
23 KiB
var TVjsApi = (function(){
|
|
var TVjsApi = function(symbol) {
|
|
var urls = 'wss://api.fcoin.com/v2/ws';
|
|
this.widgets = null;
|
|
this.socket = new socket(urls);
|
|
this.datafeeds = new datafeeds(this);
|
|
this.symbol = symbol || 'ethusdt';
|
|
this.interval = localStorage.getItem('tradingview.resolution') || '5';
|
|
this.cacheData = {};
|
|
this.lastTime = null;
|
|
this.getBarTimer = null;
|
|
this.isLoading = true;
|
|
var that = this;
|
|
this.socket.doOpen()
|
|
this.socket.on('open', function() {
|
|
//页面初始化的时候,为了快速加载,先请求150条记录
|
|
if (that.interval < 60) {
|
|
that.socket.send({
|
|
cmd: 'req',
|
|
args: ["candle.M"+that.interval+"."+symbol, 150, parseInt(Date.now() / 1000)],
|
|
id: '1366'
|
|
})
|
|
} else if (that.interval >= 60) {
|
|
that.socket.send({
|
|
cmd: 'req',
|
|
args: ["candle.H"+(that.interval/60)+"."+symbol, 150, parseInt(Date.now() / 1000)],
|
|
id: '1366'
|
|
})
|
|
} else {
|
|
that.socket.send({
|
|
cmd: 'req',
|
|
args: ["candle.D1."+symbol, 150, parseInt(Date.now() / 1000)],
|
|
id: '1366'
|
|
})
|
|
}
|
|
})
|
|
this.socket.on('message', that.onMessage.bind(this))
|
|
this.socket.on('close', that.onClose.bind(this))
|
|
}
|
|
TVjsApi.prototype.init = function() {
|
|
var resolution = this.interval;
|
|
var chartType = (localStorage.getItem('tradingview.chartType') || '1')*1;
|
|
|
|
var symbol = this.symbol;
|
|
|
|
var locale = 'en';
|
|
|
|
var skin = localStorage.getItem('tradingViewTheme') || 'black';
|
|
|
|
|
|
if (!this.widgets) {
|
|
this.widgets = new TradingView.widget({
|
|
autosize: true,
|
|
symbol: symbol,
|
|
interval: resolution,
|
|
container_id: 'tradingviewContainer',
|
|
datafeed: this.datafeeds,
|
|
library_path: '../charting_library/',
|
|
enabled_features: [],
|
|
timezone: 'Asia/Shanghai',
|
|
custom_css_url: './css/tradingview_'+skin+'.css',
|
|
locale: locale,
|
|
debug: false,
|
|
disabled_features: [
|
|
"header_symbol_search",
|
|
"header_saveload",
|
|
"header_screenshot",
|
|
"header_chart_type",
|
|
"header_compare",
|
|
"header_undo_redo",
|
|
"timeframes_toolbar",
|
|
"volume_force_overlay",
|
|
"header_resolutions",
|
|
"left_toolbar"
|
|
],
|
|
//preset: "mobile",
|
|
overrides: this.getOverrides(skin),
|
|
studies_overrides: this.getStudiesOverrides(skin)
|
|
})
|
|
|
|
var thats = this.widgets;
|
|
thats.onChartReady(function() {
|
|
createStudy();
|
|
createButton(buttons);
|
|
thats.chart().setChartType(chartType);
|
|
toggleStudy(chartType);
|
|
})
|
|
|
|
var buttons = [
|
|
{title:'Time',resolution:'1',chartType:3},
|
|
{title:'1min',resolution:'1',chartType:1},
|
|
{title:'5min',resolution:'5',chartType:1},
|
|
{title:'15min',resolution:'15',chartType:1},
|
|
{title:'30min',resolution:'30',chartType:1},
|
|
{title:'1hour',resolution:'60',chartType:1},
|
|
{title:'1day',resolution:'1D',chartType:1},
|
|
{title:'1week',resolution:'1W',chartType:1},
|
|
{title:'1month',resolution:'1M',chartType:1},
|
|
];
|
|
var studies = [];
|
|
|
|
function createButton(buttons){
|
|
for(var i = 0; i < buttons.length; i++){
|
|
(function(button){
|
|
thats.createButton()
|
|
.attr('title', button.title).addClass("mydate")
|
|
.text(button.title)
|
|
.on('click', function(e) {
|
|
if(this.parentNode.className.search('active') > -1){
|
|
return false;
|
|
}
|
|
localStorage.setItem('tradingview.resolution',button.resolution);
|
|
localStorage.setItem('tradingview.chartType',button.chartType);
|
|
var $active = this.parentNode.parentNode.querySelector('.active');
|
|
$active.className = $active.className.replace(/(\sactive|active\s)/,'');
|
|
this.parentNode.className += ' active';
|
|
thats.chart().setResolution(button.resolution, function onReadyCallback() {});
|
|
if(button.chartType != thats.chart().chartType()){
|
|
thats.chart().setChartType(button.chartType);
|
|
toggleStudy(button.chartType);
|
|
}
|
|
}).parent().addClass('my-group'+(button.resolution==resolution && button.chartType == chartType ? ' active':''));
|
|
})(buttons[i]);
|
|
}
|
|
}
|
|
function createStudy(){
|
|
var id = thats.chart().createStudy('Moving Average', false, false, [5], null, {'Plot.color': 'rgb(150, 95, 196)'});
|
|
studies.push(id);
|
|
id = thats.chart().createStudy('Moving Average', false, false, [10], null, {'Plot.color': 'rgb(116,149,187)'});
|
|
studies.push(id);
|
|
id = thats.chart().createStudy('Moving Average', false, false, [20],null,{"plot.color": "rgb(58,113,74)"});
|
|
studies.push(id);
|
|
id = thats.chart().createStudy('Moving Average', false, false, [30],null,{"plot.color": "rgb(118,32,99)"});
|
|
studies.push(id);
|
|
}
|
|
function toggleStudy(chartType){
|
|
var state = chartType == 3 ? 0 : 1;
|
|
for(var i = 0; i < studies.length; i++){
|
|
thats.chart().getStudyById(studies[i]).setVisible(state);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
TVjsApi.prototype.sendMessage = function(data) {
|
|
var that = this;
|
|
console.log("这是要发送的数据:"+JSON.stringify(data) )
|
|
if (this.socket.checkOpen()) {
|
|
this.socket.send(data)
|
|
} else {
|
|
this.socket.on('open', function() {
|
|
that.socket.send(data)
|
|
})
|
|
}
|
|
}
|
|
TVjsApi.prototype.unSubscribe = function(interval) {
|
|
var thats = this;
|
|
//停止订阅,删除过期缓存、缓存时间、缓存状态
|
|
var ticker = thats.symbol + "-" + interval;
|
|
var tickertime = ticker + "load";
|
|
var tickerstate = ticker + "state";
|
|
var tickerCallback = ticker + "Callback";
|
|
delete thats.cacheData[ticker];
|
|
delete thats.cacheData[tickertime];
|
|
delete thats.cacheData[tickerstate];
|
|
delete thats.cacheData[tickerCallback];
|
|
if (interval < 60) {
|
|
this.sendMessage({
|
|
cmd: 'unsub',
|
|
args: ["candle.M" + interval + "." + this.symbol.toLowerCase()]
|
|
})
|
|
} else if (interval >= 60) {
|
|
this.sendMessage({
|
|
cmd: 'unsub',
|
|
args: ["candle.H" + (interval / 60) + "." + this.symbol.toLowerCase()]
|
|
})
|
|
} else {
|
|
this.sendMessage({
|
|
cmd: 'unsub',
|
|
args: ["candle.D1." + this.symbol.toLowerCase()]
|
|
})
|
|
}
|
|
}
|
|
TVjsApi.prototype.subscribe = function() {
|
|
if (this.interval < 60) {
|
|
this.sendMessage({
|
|
cmd: 'sub',
|
|
args: ["candle.M" + this.interval + "." + this.symbol.toLowerCase()],
|
|
})
|
|
} else if (this.interval >= 60) {
|
|
this.sendMessage({
|
|
cmd: 'sub',
|
|
args: ["candle.H" + (this.interval / 60) + "." + this.symbol.toLowerCase()],
|
|
})
|
|
} else {
|
|
this.sendMessage({
|
|
cmd: 'sub',
|
|
args: ["candle.D1." + this.symbol.toLowerCase()],
|
|
})
|
|
}
|
|
}
|
|
TVjsApi.prototype.onMessage = function(data) {
|
|
var thats = this;
|
|
//通知app
|
|
/*if(thats.trade.update){
|
|
thats.trade.update(data);
|
|
}*/
|
|
// console.log("这是后台返回的数据"+count+":"+JSON.stringify(data) )
|
|
|
|
if (data.data && data.data.length) {
|
|
//websocket返回的值,数组代表时间段历史数据,不是增量
|
|
var list = []
|
|
var ticker = thats.symbol + "-" + thats.interval;
|
|
var tickerstate = ticker + "state";
|
|
var tickerCallback = ticker + "Callback";
|
|
var onLoadedCallback = thats.cacheData[tickerCallback];
|
|
|
|
//var that = thats;
|
|
//遍历数组,构造缓存数据
|
|
data.data.forEach(function(element) {
|
|
list.push({
|
|
time: element.id*1000,
|
|
open: element.open,
|
|
high: element.high,
|
|
low: element.low,
|
|
close: element.close,
|
|
volume: element.quote_vol
|
|
})
|
|
}, thats)
|
|
//如果没有缓存数据,则直接填充,发起订阅
|
|
if(!thats.cacheData[ticker]){
|
|
/*thats.cacheData[ticker] = thats.cacheData[ticker].concat(list);
|
|
thats.cacheData['onLoadedCallback'](list);
|
|
}else{*/
|
|
thats.cacheData[ticker] = list;
|
|
thats.subscribe()
|
|
}
|
|
//新数据即当前时间段需要的数据,直接喂给图表插件
|
|
if(onLoadedCallback){
|
|
onLoadedCallback(list);
|
|
delete thats.cacheData[tickerCallback];
|
|
}
|
|
//请求完成,设置状态为false
|
|
thats.cacheData[tickerstate] = !1;
|
|
//记录当前缓存时间,即数组最后一位的时间
|
|
thats.lastTime = thats.cacheData[ticker][thats.cacheData[ticker].length - 1].time
|
|
}
|
|
if (data.type && data.type.indexOf(thats.symbol.toLowerCase()) !== -1) {
|
|
//console.log(' >> sub:', data.type)
|
|
//console.log(' >> interval:', thats.interval)
|
|
// data带有type,即返回的是订阅数据,
|
|
//缓存的key
|
|
var ticker = thats.symbol + "-" + thats.interval;
|
|
//构造增量更新数据
|
|
var barsData = {
|
|
time: data.id * 1000,
|
|
open: data.open,
|
|
high: data.high,
|
|
low: data.low,
|
|
close: data.close,
|
|
volume: data.quote_vol
|
|
}
|
|
/*if (barsData.time >= thats.lastTime && thats.cacheData[ticker] && thats.cacheData[ticker].length) {
|
|
thats.cacheData[ticker][thats.cacheData[ticker].length - 1] = barsData
|
|
}*/
|
|
//如果增量更新数据的时间大于缓存时间,而且缓存有数据,数据长度大于0
|
|
if (barsData.time > thats.lastTime && thats.cacheData[ticker] && thats.cacheData[ticker].length) {
|
|
//增量更新的数据直接加入缓存数组
|
|
thats.cacheData[ticker].push(barsData)
|
|
//修改缓存时间
|
|
thats.lastTime = barsData.time
|
|
}else if(barsData.time == thats.lastTime && thats.cacheData[ticker] && thats.cacheData[ticker].length){
|
|
//如果增量更新的时间等于缓存时间,即在当前时间颗粒内产生了新数据,更新当前数据
|
|
thats.cacheData[ticker][thats.cacheData[ticker].length - 1] = barsData
|
|
}
|
|
// 通知图表插件,可以开始增量更新的渲染了
|
|
thats.datafeeds.barsUpdater.updateData()
|
|
}else if(data.status){
|
|
var ticker = thats.symbol + "-" + thats.interval;
|
|
var tickerCallback = ticker + "Callback";
|
|
var onLoadedCallback = thats.cacheData[tickerCallback];
|
|
if(onLoadedCallback){
|
|
onLoadedCallback([]);
|
|
delete thats.cacheData[tickerCallback];
|
|
}
|
|
}
|
|
}
|
|
TVjsApi.prototype.onClose = function(){
|
|
var thats = this;
|
|
console.log(' >> : 连接已断开... 正在重连')
|
|
thats.socket.doOpen()
|
|
thats.socket.on('open', function() {
|
|
console.log(' >> : 已重连')
|
|
thats.subscribe()
|
|
});
|
|
}
|
|
TVjsApi.prototype.initMessage = function(symbolInfo, resolution, rangeStartDate, rangeEndDate, onLoadedCallback){
|
|
//console.log('发起请求,从websocket获取当前时间段的数据');
|
|
var that = this;
|
|
//保留当前回调
|
|
var tickerCallback = this.symbol + "-" + resolution + "Callback";
|
|
that.cacheData[tickerCallback] = onLoadedCallback;
|
|
//获取需要请求的数据数目
|
|
var limit = that.initLimit(resolution, rangeStartDate, rangeEndDate);
|
|
//商品名
|
|
var symbol = that.symbol;
|
|
//如果当前时间节点已经改变,停止上一个时间节点的订阅,修改时间节点值
|
|
if(that.interval !== resolution){
|
|
that.unSubscribe(that.interval)
|
|
that.interval = resolution;
|
|
}
|
|
//获取当前时间段的数据,在onMessage中执行回调onLoadedCallback
|
|
if (that.interval < 60) {
|
|
that.socket.send({
|
|
cmd: 'req',
|
|
args: ["candle.M"+resolution+"."+symbol, limit, rangeEndDate],
|
|
id: 'trade.M'+ resolution +'.'+ symbol.toLowerCase()
|
|
})
|
|
} else if (that.interval >= 60) {
|
|
that.socket.send({
|
|
cmd: 'req',
|
|
args: ["candle.H"+(resolution/60)+"."+symbol, limit, rangeEndDate],
|
|
id: 'trade.H'+ (resolution / 60) +'.'+ symbol.toLowerCase()
|
|
})
|
|
} else {
|
|
that.socket.send({
|
|
cmd: 'req',
|
|
args: ["candle.D1."+symbol, limit, rangeEndDate],
|
|
id: 'trade.D1.'+ symbol.toLowerCase()
|
|
})
|
|
}
|
|
}
|
|
TVjsApi.prototype.initLimit = function(resolution, rangeStartDate, rangeEndDate){
|
|
var limit = 0;
|
|
switch(resolution){
|
|
case '1D' : limit = Math.ceil((rangeEndDate - rangeStartDate) / 60 / 60 / 24); break;
|
|
case '1W' : limit = Math.ceil((rangeEndDate - rangeStartDate) / 60 / 60 / 24 / 7); break;
|
|
case '1M' : limit = Math.ceil((rangeEndDate - rangeStartDate) / 60 / 60 / 24 / 31); break;
|
|
default : limit = Math.ceil((rangeEndDate - rangeStartDate) / 60 / resolution); break;
|
|
}
|
|
return limit;
|
|
}
|
|
TVjsApi.prototype.getBars = function(symbolInfo, resolution, rangeStartDate, rangeEndDate, onLoadedCallback) {
|
|
//console.log(' >> :', rangeStartDate, rangeEndDate)
|
|
var ticker = this.symbol + "-" + resolution;
|
|
var tickerload = ticker + "load";
|
|
var tickerstate = ticker + "state";
|
|
|
|
if(!this.cacheData[ticker] && !this.cacheData[tickerstate]){
|
|
//如果缓存没有数据,而且未发出请求,记录当前节点开始时间
|
|
this.cacheData[tickerload] = rangeStartDate;
|
|
//发起请求,从websocket获取当前时间段的数据
|
|
this.initMessage(symbolInfo, resolution, rangeStartDate, rangeEndDate, onLoadedCallback);
|
|
//设置状态为true
|
|
this.cacheData[tickerstate] = !0;
|
|
return false;
|
|
}
|
|
if(!this.cacheData[tickerload] || this.cacheData[tickerload] > rangeStartDate){
|
|
//如果缓存有数据,但是没有当前时间段的数据,更新当前节点时间
|
|
this.cacheData[tickerload] = rangeStartDate;
|
|
//发起请求,从websocket获取当前时间段的数据
|
|
this.initMessage(symbolInfo, resolution, rangeStartDate, rangeEndDate, onLoadedCallback);
|
|
//设置状态为true
|
|
this.cacheData[tickerstate] = !0;
|
|
return false;
|
|
}
|
|
if(this.cacheData[tickerstate]){
|
|
//正在从websocket获取数据,禁止一切操作
|
|
return false;
|
|
}
|
|
ticker = this.symbol + "-" + this.interval;
|
|
if (this.cacheData[ticker] && this.cacheData[ticker].length) {
|
|
this.isLoading = false
|
|
var newBars = []
|
|
this.cacheData[ticker].forEach(item => {
|
|
if (item.time >= rangeStartDate * 1000 && item.time <= rangeEndDate * 1000) {
|
|
newBars.push(item)
|
|
}
|
|
})
|
|
onLoadedCallback(newBars)
|
|
} else {
|
|
var self = this
|
|
this.getBarTimer = setTimeout(function() {
|
|
self.getBars(symbolInfo, resolution, rangeStartDate, rangeEndDate, onLoadedCallback)
|
|
}, 10)
|
|
}
|
|
}
|
|
TVjsApi.prototype.getOverrides = function(theme){
|
|
var themes = {
|
|
"white": {
|
|
up: "#03c087",
|
|
down: "#ef5555",
|
|
bg: "#ffffff",
|
|
grid: "#f7f8fa",
|
|
cross: "#23283D",
|
|
border: "#9194a4",
|
|
text: "#9194a4",
|
|
areatop: "rgba(71, 78, 112, 0.1)",
|
|
areadown: "rgba(71, 78, 112, 0.02)",
|
|
line: "#737375"
|
|
},
|
|
"black": {
|
|
up: "#589065",
|
|
down: "#ae4e54",
|
|
bg: "#181B2A",
|
|
grid: "#1f2943",
|
|
cross: "#9194A3",
|
|
border: "#4e5b85",
|
|
text: "#61688A",
|
|
areatop: "rgba(122, 152, 247, .1)",
|
|
areadown: "rgba(122, 152, 247, .02)",
|
|
line: "#737375"
|
|
},
|
|
"mobile": {
|
|
up: "#03C087",
|
|
down: "#E76D42",
|
|
bg: "#ffffff",
|
|
grid: "#f7f8fa",
|
|
cross: "#23283D",
|
|
border: "#C5CFD5",
|
|
text: "#8C9FAD",
|
|
areatop: "rgba(71, 78, 112, 0.1)",
|
|
areadown: "rgba(71, 78, 112, 0.02)",
|
|
showLegend: !0
|
|
}
|
|
};
|
|
var t = themes[theme];
|
|
return {
|
|
"volumePaneSize": "medium",
|
|
"scalesProperties.lineColor": t.text,
|
|
"scalesProperties.textColor": t.text,
|
|
"paneProperties.background": t.bg,
|
|
"paneProperties.vertGridProperties.color": t.grid,
|
|
"paneProperties.horzGridProperties.color": t.grid,
|
|
"paneProperties.crossHairProperties.color": t.cross,
|
|
"paneProperties.legendProperties.showLegend": !!t.showLegend,
|
|
"paneProperties.legendProperties.showStudyArguments": !0,
|
|
"paneProperties.legendProperties.showStudyTitles": !0,
|
|
"paneProperties.legendProperties.showStudyValues": !0,
|
|
"paneProperties.legendProperties.showSeriesTitle": !0,
|
|
"paneProperties.legendProperties.showSeriesOHLC": !0,
|
|
"mainSeriesProperties.candleStyle.upColor": t.up,
|
|
"mainSeriesProperties.candleStyle.downColor": t.down,
|
|
"mainSeriesProperties.candleStyle.drawWick": !0,
|
|
"mainSeriesProperties.candleStyle.drawBorder": !0,
|
|
"mainSeriesProperties.candleStyle.borderColor": t.border,
|
|
"mainSeriesProperties.candleStyle.borderUpColor": t.up,
|
|
"mainSeriesProperties.candleStyle.borderDownColor": t.down,
|
|
"mainSeriesProperties.candleStyle.wickUpColor": t.up,
|
|
"mainSeriesProperties.candleStyle.wickDownColor": t.down,
|
|
"mainSeriesProperties.candleStyle.barColorsOnPrevClose": !1,
|
|
"mainSeriesProperties.hollowCandleStyle.upColor": t.up,
|
|
"mainSeriesProperties.hollowCandleStyle.downColor": t.down,
|
|
"mainSeriesProperties.hollowCandleStyle.drawWick": !0,
|
|
"mainSeriesProperties.hollowCandleStyle.drawBorder": !0,
|
|
"mainSeriesProperties.hollowCandleStyle.borderColor": t.border,
|
|
"mainSeriesProperties.hollowCandleStyle.borderUpColor": t.up,
|
|
"mainSeriesProperties.hollowCandleStyle.borderDownColor": t.down,
|
|
"mainSeriesProperties.hollowCandleStyle.wickColor": t.line,
|
|
"mainSeriesProperties.haStyle.upColor": t.up,
|
|
"mainSeriesProperties.haStyle.downColor": t.down,
|
|
"mainSeriesProperties.haStyle.drawWick": !0,
|
|
"mainSeriesProperties.haStyle.drawBorder": !0,
|
|
"mainSeriesProperties.haStyle.borderColor": t.border,
|
|
"mainSeriesProperties.haStyle.borderUpColor": t.up,
|
|
"mainSeriesProperties.haStyle.borderDownColor": t.down,
|
|
"mainSeriesProperties.haStyle.wickColor": t.border,
|
|
"mainSeriesProperties.haStyle.barColorsOnPrevClose": !1,
|
|
"mainSeriesProperties.barStyle.upColor": t.up,
|
|
"mainSeriesProperties.barStyle.downColor": t.down,
|
|
"mainSeriesProperties.barStyle.barColorsOnPrevClose": !1,
|
|
"mainSeriesProperties.barStyle.dontDrawOpen": !1,
|
|
"mainSeriesProperties.lineStyle.color": t.border,
|
|
"mainSeriesProperties.lineStyle.linewidth": 1,
|
|
"mainSeriesProperties.lineStyle.priceSource": "close",
|
|
"mainSeriesProperties.areaStyle.color1": t.areatop,
|
|
"mainSeriesProperties.areaStyle.color2": t.areadown,
|
|
"mainSeriesProperties.areaStyle.linecolor": t.border,
|
|
"mainSeriesProperties.areaStyle.linewidth": 1,
|
|
"mainSeriesProperties.areaStyle.priceSource": "close"
|
|
}
|
|
}
|
|
TVjsApi.prototype.getStudiesOverrides = function(theme){
|
|
var themes = {
|
|
"white": {
|
|
c0: "#eb4d5c",
|
|
c1: "#53b987",
|
|
t: 70,
|
|
v: !1
|
|
},
|
|
"black": {
|
|
c0: "#fd8b8b",
|
|
c1: "#3cb595",
|
|
t: 70,
|
|
v: !1
|
|
}
|
|
};
|
|
var t = themes[theme];
|
|
return {
|
|
"volume.volume.color.0": t.c0,
|
|
"volume.volume.color.1": t.c1,
|
|
"volume.volume.transparency": t.t,
|
|
"volume.options.showStudyArguments": t.v
|
|
}
|
|
}
|
|
TVjsApi.prototype.resetTheme = function(skin){
|
|
this.widgets.addCustomCSSFile('./css/tradingview_'+skin+'.css');
|
|
this.widgets.applyOverride(this.getOverrides(skin));
|
|
this.widgets.applyStudiesOverrides(this.getStudiesOverrides(skin));
|
|
}
|
|
TVjsApi.prototype.formatt = function(time){
|
|
if(isNaN(time)){
|
|
return time;
|
|
}
|
|
var date = new Date(time);
|
|
var Y = date.getFullYear();
|
|
var m = this._formatt(date.getMonth());
|
|
var d = this._formatt(date.getDate());
|
|
var H = this._formatt(date.getHours());
|
|
var i = this._formatt(date.getMinutes());
|
|
var s = this._formatt(date.getSeconds());
|
|
return Y+'-'+m+'-'+d+' '+H+':'+i+':'+s;
|
|
}
|
|
TVjsApi.prototype._formatt = function(num){
|
|
return num >= 10 ? num : '0'+num;
|
|
}
|
|
return TVjsApi;
|
|
})();
|
|
|