diff --git a/frontend/src/app/components/app/app.component.ts b/frontend/src/app/components/app/app.component.ts
index 633e466a4..d9d6f77d6 100644
--- a/frontend/src/app/components/app/app.component.ts
+++ b/frontend/src/app/components/app/app.component.ts
@@ -25,6 +25,8 @@ export class AppComponent implements OnInit {
if (this.locale.startsWith('ar') || this.locale.startsWith('fa') || this.locale.startsWith('he')) {
this.dir = 'rtl';
this.class = 'rtl-layout';
+ } else {
+ this.class = 'ltr-layout';
}
tooltipConfig.animation = false;
diff --git a/frontend/src/app/components/blockchain/blockchain.component.scss b/frontend/src/app/components/blockchain/blockchain.component.scss
index 2f13374fe..df609ff40 100644
--- a/frontend/src/app/components/blockchain/blockchain.component.scss
+++ b/frontend/src/app/components/blockchain/blockchain.component.scss
@@ -27,7 +27,6 @@
left: 0;
top: 75px;
transform: translateX(50vw);
- transition: transform 1s;
}
.position-container.liquid, .position-container.liquidtestnet {
@@ -84,9 +83,9 @@
.time-toggle {
color: white;
- font-size: 1rem;
+ font-size: 0.8rem;
position: absolute;
- bottom: -1.5em;
+ bottom: -1.8em;
left: 1px;
transform: translateX(-50%);
background: none;
@@ -97,14 +96,31 @@
}
.blockchain-wrapper.ltr-transition .blocks-wrapper,
+.blockchain-wrapper.ltr-transition .position-container,
.blockchain-wrapper.ltr-transition .time-toggle {
transition: transform 1s;
}
-.blockchain-wrapper.time-ltr .blocks-wrapper {
- transform: scaleX(-1);
+.blockchain-wrapper.time-ltr {
+ .blocks-wrapper {
+ transform: scaleX(-1);
+ }
+
+ .time-toggle {
+ transform: translateX(-50%) scaleX(-1);
+ }
}
-.blockchain-wrapper.time-ltr .time-toggle {
- transform: translateX(-50%) scaleX(-1);
+:host-context(.ltr-layout) {
+ .blockchain-wrapper.time-ltr .blocks-wrapper,
+ .blockchain-wrapper .blocks-wrapper {
+ direction: ltr;
+ }
+}
+
+:host-context(.rtl-layout) {
+ .blockchain-wrapper.time-ltr .blocks-wrapper,
+ .blockchain-wrapper .blocks-wrapper {
+ direction: rtl;
+ }
}
\ No newline at end of file
diff --git a/frontend/src/app/components/mempool-blocks/mempool-blocks.component.scss b/frontend/src/app/components/mempool-blocks/mempool-blocks.component.scss
index 8032be92f..565d4b302 100644
--- a/frontend/src/app/components/mempool-blocks/mempool-blocks.component.scss
+++ b/frontend/src/app/components/mempool-blocks/mempool-blocks.component.scss
@@ -146,4 +146,10 @@
.block-body {
transform: scaleX(-1);
}
+}
+
+:host-context(.rtl-layout) {
+ #arrow-up {
+ transform: translateX(70px);
+ }
}
\ No newline at end of file
diff --git a/frontend/src/app/components/start/start.component.html b/frontend/src/app/components/start/start.component.html
index 9d7f39ba2..89b6efdc3 100644
--- a/frontend/src/app/components/start/start.component.html
+++ b/frontend/src/app/components/start/start.component.html
@@ -8,7 +8,7 @@
0" class="warning-label">{{ eventName }} in {{ countdown | number }} block{{ countdown === 1 ? '' : 's' }}!
-
diff --git a/frontend/src/app/components/start/start.component.ts b/frontend/src/app/components/start/start.component.ts
index 78e004985..37c94baa3 100644
--- a/frontend/src/app/components/start/start.component.ts
+++ b/frontend/src/app/components/start/start.component.ts
@@ -1,4 +1,5 @@
-import { Component, ElementRef, HostListener, OnInit, ViewChild } from '@angular/core';
+import { Component, ElementRef, HostListener, OnInit, OnDestroy, ViewChild } from '@angular/core';
+import { Subscription } from 'rxjs';
import { StateService } from '../../services/state.service';
import { specialBlocks } from '../../app.constants';
@@ -7,7 +8,7 @@ import { specialBlocks } from '../../app.constants';
templateUrl: './start.component.html',
styleUrls: ['./start.component.scss'],
})
-export class StartComponent implements OnInit {
+export class StartComponent implements OnInit, OnDestroy {
interval = 60;
colors = ['#5E35B1', '#ffffff'];
@@ -16,6 +17,8 @@ export class StartComponent implements OnInit {
eventName = '';
mouseDragStartX: number;
blockchainScrollLeftInit: number;
+ timeLtrSubscription: Subscription;
+ timeLtr: boolean = this.stateService.timeLtr.value;
@ViewChild('blockchainContainer') blockchainContainer: ElementRef;
constructor(
@@ -23,6 +26,9 @@ export class StartComponent implements OnInit {
) { }
ngOnInit() {
+ this.timeLtrSubscription = this.stateService.timeLtr.subscribe((ltr) => {
+ this.timeLtr = !!ltr;
+ });
this.stateService.blocks$
.subscribe((blocks: any) => {
if (this.stateService.network !== '') {
@@ -72,4 +78,8 @@ export class StartComponent implements OnInit {
this.mouseDragStartX = null;
this.stateService.setBlockScrollingInProgress(false);
}
+
+ ngOnDestroy() {
+ this.timeLtrSubscription.unsubscribe();
+ }
}
diff --git a/frontend/src/app/components/transaction/transaction.component.html b/frontend/src/app/components/transaction/transaction.component.html
index 5b35fc7b1..360d3e34f 100644
--- a/frontend/src/app/components/transaction/transaction.component.html
+++ b/frontend/src/app/components/transaction/transaction.component.html
@@ -195,7 +195,7 @@
Flow
-
+
@@ -208,7 +208,11 @@
[lineLimit]="inOutLimit"
[maxStrands]="graphExpanded ? maxInOut : 24"
[network]="network"
- [tooltip]="true">
+ [tooltip]="true"
+ [inputIndex]="inputIndex" [outputIndex]="outputIndex"
+ (selectInput)="selectInput($event)"
+ (selectOutput)="selectOutput($event)"
+ >
24">
@@ -234,13 +238,13 @@
-
+
-
+
Details
diff --git a/frontend/src/app/components/transaction/transaction.component.scss b/frontend/src/app/components/transaction/transaction.component.scss
index e0e7d79fe..df8d37ebc 100644
--- a/frontend/src/app/components/transaction/transaction.component.scss
+++ b/frontend/src/app/components/transaction/transaction.component.scss
@@ -3,38 +3,38 @@
}
.container-buttons {
- align-self: center;
+ align-self: flex-start;
}
.title-block {
- flex-wrap: wrap;
+ flex-wrap: wrap;
+ align-items: baseline;
@media (min-width: 650px) {
flex-direction: row;
}
h1 {
margin: 0rem;
+ margin-right: 15px;
line-height: 1;
}
}
.tx-link {
- display: flex;
- flex-grow: 1;
margin-bottom: 0px;
margin-top: 8px;
- @media (min-width: 650px) {
- align-self: end;
- margin-left: 15px;
- margin-top: 0px;
- margin-bottom: -3px;
- }
- @media (min-width: 768px) {
+ display: inline-block;
+ width: 100%;
+ flex-shrink: 0;
+ @media (min-width: 651px) {
+ display: flex;
+ width: auto;
+ flex-grow: 1;
margin-bottom: 0px;
top: 1px;
position: relative;
- }
- @media (max-width: 768px) {
- order: 3;
- }
+ }
+ @media (max-width: 650px) {
+ order: 3;
+ }
}
.td-width {
diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts
index 1db6e8f09..c64c112b1 100644
--- a/frontend/src/app/components/transaction/transaction.component.ts
+++ b/frontend/src/app/components/transaction/transaction.component.ts
@@ -47,6 +47,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
now = new Date().getTime();
timeAvg$: Observable
;
liquidUnblinding = new LiquidUnblinding();
+ inputIndex: number;
outputIndex: number;
showFlow: boolean = true;
graphExpanded: boolean = false;
@@ -121,8 +122,15 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
.pipe(
switchMap((params: ParamMap) => {
const urlMatch = (params.get('id') || '').split(':');
- this.txId = urlMatch[0];
- this.outputIndex = urlMatch[1] === undefined ? null : parseInt(urlMatch[1], 10);
+ if (urlMatch.length === 2 && urlMatch[1].length === 64) {
+ this.inputIndex = parseInt(urlMatch[0], 10);
+ this.outputIndex = null;
+ this.txId = urlMatch[1];
+ } else {
+ this.txId = urlMatch[0];
+ this.outputIndex = urlMatch[1] === undefined ? null : parseInt(urlMatch[1], 10);
+ this.inputIndex = null;
+ }
this.seoService.setTitle(
$localize`:@@bisq.transaction.browser-title:Transaction: ${this.txId}:INTERPOLATION:`
);
@@ -334,6 +342,16 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
this.graphExpanded = false;
}
+ selectInput(input) {
+ this.inputIndex = input;
+ this.outputIndex = null;
+ }
+
+ selectOutput(output) {
+ this.outputIndex = output;
+ this.inputIndex = null;
+ }
+
@HostListener('window:resize', ['$event'])
setGraphSize(): void {
if (this.graphContainer) {
diff --git a/frontend/src/app/components/transactions-list/transactions-list.component.html b/frontend/src/app/components/transactions-list/transactions-list.component.html
index 81c3dce5c..e53c54a7a 100644
--- a/frontend/src/app/components/transactions-list/transactions-list.component.html
+++ b/frontend/src/app/components/transactions-list/transactions-list.component.html
@@ -20,9 +20,9 @@
- rowLimit) ? tx.vin.slice(0, rowLimit - 2) : tx.vin.slice(0, rowLimit)) : tx.vin" [ngForTrackBy]="trackByIndexFn">
+ inputRowLimit) ? tx.vin.slice(0, inputRowLimit - 2) : tx.vin.slice(0, inputRowLimit)) : tx.vin" [ngForTrackBy]="trackByIndexFn">
@@ -146,7 +146,7 @@
|
- rowLimit && tx['@vinLimit']">
+
inputRowLimit && tx['@vinLimit']">
|
@@ -158,7 +158,7 @@
- rowLimit) ? tx.vout.slice(0, rowLimit - 2) : tx.vout.slice(0, rowLimit)) : tx.vout" [ngForTrackBy]="trackByIndexFn">
+ outputRowLimit) ? tx.vout.slice(0, outputRowLimit - 2) : tx.vout.slice(0, outputRowLimit)) : tx.vout" [ngForTrackBy]="trackByIndexFn">
-
+
@@ -257,7 +257,7 @@
- rowLimit && tx['@voutLimit'] && !outputIndex">
+
outputRowLimit && tx['@voutLimit']">
|
diff --git a/frontend/src/app/components/transactions-list/transactions-list.component.ts b/frontend/src/app/components/transactions-list/transactions-list.component.ts
index 8fd81af51..4f3f1cec3 100644
--- a/frontend/src/app/components/transactions-list/transactions-list.component.ts
+++ b/frontend/src/app/components/transactions-list/transactions-list.component.ts
@@ -24,6 +24,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
@Input() transactionPage = false;
@Input() errorUnblinded = false;
@Input() paginated = false;
+ @Input() inputIndex: number;
@Input() outputIndex: number;
@Input() address: string = '';
@Input() rowLimit = 12;
@@ -37,6 +38,8 @@ export class TransactionsListComponent implements OnInit, OnChanges {
showDetails$ = new BehaviorSubject(false);
assetsMinimal: any;
transactionsLength: number = 0;
+ inputRowLimit: number = 12;
+ outputRowLimit: number = 12;
constructor(
public stateService: StateService,
@@ -97,50 +100,57 @@ export class TransactionsListComponent implements OnInit, OnChanges {
).subscribe(() => this.ref.markForCheck());
}
- ngOnChanges(): void {
- if (!this.transactions || !this.transactions.length) {
- return;
+ ngOnChanges(changes): void {
+ if (changes.inputIndex || changes.outputIndex || changes.rowLimit) {
+ this.inputRowLimit = Math.max(this.rowLimit, (this.inputIndex || 0) + 3);
+ this.outputRowLimit = Math.max(this.rowLimit, (this.outputIndex || 0) + 3);
+ if ((this.inputIndex || this.outputIndex) && !changes.transactions) {
+ setTimeout(() => {
+ const assetBoxElements = document.getElementsByClassName('assetBox');
+ if (assetBoxElements && assetBoxElements[0]) {
+ assetBoxElements[0].scrollIntoView({block: "center"});
+ }
+ }, 10);
+ }
}
-
- this.transactionsLength = this.transactions.length;
- if (this.outputIndex) {
- setTimeout(() => {
- const assetBoxElements = document.getElementsByClassName('assetBox');
- if (assetBoxElements && assetBoxElements[0]) {
- assetBoxElements[0].scrollIntoView();
- }
- }, 10);
- }
-
- this.transactions.forEach((tx) => {
- tx['@voutLimit'] = true;
- tx['@vinLimit'] = true;
- if (tx['addressValue'] !== undefined) {
+ if (changes.transactions || changes.address) {
+ if (!this.transactions || !this.transactions.length) {
return;
}
- if (this.address) {
- const addressIn = tx.vout
- .filter((v: Vout) => v.scriptpubkey_address === this.address)
- .map((v: Vout) => v.value || 0)
- .reduce((a: number, b: number) => a + b, 0);
+ this.transactionsLength = this.transactions.length;
- const addressOut = tx.vin
- .filter((v: Vin) => v.prevout && v.prevout.scriptpubkey_address === this.address)
- .map((v: Vin) => v.prevout.value || 0)
- .reduce((a: number, b: number) => a + b, 0);
- tx['addressValue'] = addressIn - addressOut;
- }
- });
- const txIds = this.transactions.filter((tx) => !tx._outspends).map((tx) => tx.txid);
- if (txIds.length) {
- this.refreshOutspends$.next(txIds);
- }
- if (this.stateService.env.LIGHTNING) {
- const txIds = this.transactions.filter((tx) => !tx._channels).map((tx) => tx.txid);
+ this.transactions.forEach((tx) => {
+ tx['@voutLimit'] = true;
+ tx['@vinLimit'] = true;
+ if (tx['addressValue'] !== undefined) {
+ return;
+ }
+
+ if (this.address) {
+ const addressIn = tx.vout
+ .filter((v: Vout) => v.scriptpubkey_address === this.address)
+ .map((v: Vout) => v.value || 0)
+ .reduce((a: number, b: number) => a + b, 0);
+
+ const addressOut = tx.vin
+ .filter((v: Vin) => v.prevout && v.prevout.scriptpubkey_address === this.address)
+ .map((v: Vin) => v.prevout.value || 0)
+ .reduce((a: number, b: number) => a + b, 0);
+
+ tx['addressValue'] = addressIn - addressOut;
+ }
+ });
+ const txIds = this.transactions.filter((tx) => !tx._outspends).map((tx) => tx.txid);
if (txIds.length) {
- this.refreshChannels$.next(txIds);
+ this.refreshOutspends$.next(txIds);
+ }
+ if (this.stateService.env.LIGHTNING) {
+ const txIds = this.transactions.filter((tx) => !tx._channels).map((tx) => tx.txid);
+ if (txIds.length) {
+ this.refreshChannels$.next(txIds);
+ }
}
}
}
diff --git a/frontend/src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.html b/frontend/src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.html
index 563e6ed00..6872438a0 100644
--- a/frontend/src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.html
+++ b/frontend/src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.html
@@ -44,7 +44,7 @@
Output
Fee
- #{{ line.index }}
+ #{{ line.index + 1 }}
Confidential
diff --git a/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.html b/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.html
index 03056cd53..ced3b5f57 100644
--- a/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.html
+++ b/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.html
@@ -41,6 +41,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
@@ -56,20 +68,24 @@
diff --git a/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.scss b/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.scss
index 9cacb7d4b..5a71ee421 100644
--- a/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.scss
+++ b/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.scss
@@ -12,6 +12,17 @@
stroke: url(#fee-gradient);
}
+ &.highlight {
+ z-index: 8;
+ cursor: pointer;
+ &.input {
+ stroke: url(#input-highlight-gradient);
+ }
+ &.output {
+ stroke: url(#output-highlight-gradient);
+ }
+ }
+
&:hover {
z-index: 10;
cursor: pointer;
diff --git a/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.ts b/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.ts
index 16e2736f7..9d29500f0 100644
--- a/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.ts
+++ b/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.ts
@@ -1,5 +1,11 @@
-import { Component, OnInit, Input, OnChanges, HostListener } from '@angular/core';
-import { Transaction } from '../../interfaces/electrs.interface';
+import { Component, OnInit, Input, Output, EventEmitter, OnChanges, HostListener } from '@angular/core';
+import { StateService } from '../../services/state.service';
+import { Outspend, Transaction } from '../../interfaces/electrs.interface';
+import { Router } from '@angular/router';
+import { ReplaySubject, merge, Subscription } from 'rxjs';
+import { tap, switchMap } from 'rxjs/operators';
+import { ApiService } from '../../services/api.service';
+import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
interface SvgLine {
path: string;
@@ -34,6 +40,11 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
@Input() minWeight = 2; //
@Input() maxStrands = 24; // number of inputs/outputs to keep fully on-screen.
@Input() tooltip = false;
+ @Input() inputIndex: number;
+ @Input() outputIndex: number;
+
+ @Output() selectInput = new EventEmitter();
+ @Output() selectOutput = new EventEmitter();
inputData: Xput[];
outputData: Xput[];
@@ -45,6 +56,10 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
isLiquid: boolean = false;
hoverLine: Xput | void = null;
tooltipPosition = { x: 0, y: 0 };
+ outspends: Outspend[] = [];
+
+ outspendsSubscription: Subscription;
+ refreshOutspends$: ReplaySubject = new ReplaySubject();
gradientColors = {
'': ['#9339f4', '#105fb0'],
@@ -61,12 +76,45 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
gradient: string[] = ['#105fb0', '#105fb0'];
+ constructor(
+ private router: Router,
+ private relativeUrlPipe: RelativeUrlPipe,
+ private stateService: StateService,
+ private apiService: ApiService,
+ ) { }
+
ngOnInit(): void {
this.initGraph();
+
+ this.outspendsSubscription = merge(
+ this.refreshOutspends$
+ .pipe(
+ switchMap((txid) => this.apiService.getOutspendsBatched$([txid])),
+ tap((outspends: Outspend[][]) => {
+ if (!this.tx || !outspends || !outspends.length) {
+ return;
+ }
+ this.outspends = outspends[0];
+ }),
+ ),
+ this.stateService.utxoSpent$
+ .pipe(
+ tap((utxoSpent) => {
+ for (const i in utxoSpent) {
+ this.outspends[i] = {
+ spent: true,
+ txid: utxoSpent[i].txid,
+ vin: utxoSpent[i].vin,
+ };
+ }
+ }),
+ ),
+ ).subscribe(() => {});
}
ngOnChanges(): void {
this.initGraph();
+ this.refreshOutspends$.next(this.tx.txid);
}
initGraph(): void {
@@ -76,11 +124,12 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
this.combinedWeight = Math.min(this.maxCombinedWeight, Math.floor((this.width - (2 * this.midWidth)) / 6));
const totalValue = this.calcTotalValue(this.tx);
- let voutWithFee = this.tx.vout.map(v => {
+ let voutWithFee = this.tx.vout.map((v, i) => {
return {
type: v.scriptpubkey_type === 'fee' ? 'fee' : 'output',
value: v?.value,
address: v?.scriptpubkey_address || v?.scriptpubkey_type?.toUpperCase(),
+ index: i,
pegout: v?.pegout?.scriptpubkey_address,
confidential: (this.isLiquid && v?.value === undefined),
} as Xput;
@@ -91,11 +140,12 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
}
const outputCount = voutWithFee.length;
- let truncatedInputs = this.tx.vin.map(v => {
+ let truncatedInputs = this.tx.vin.map((v, i) => {
return {
type: 'input',
value: v?.prevout?.value,
address: v?.prevout?.scriptpubkey_address || v?.prevout?.scriptpubkey_type?.toUpperCase(),
+ index: i,
coinbase: v?.is_coinbase,
pegin: v?.is_pegin,
confidential: (this.isLiquid && v?.prevout?.value === undefined),
@@ -306,8 +356,7 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
};
} else {
this.hoverLine = {
- ...this.outputData[index],
- index
+ ...this.outputData[index]
};
}
}
@@ -315,4 +364,29 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
onBlur(event, side, index): void {
this.hoverLine = null;
}
+
+ onClick(event, side, index): void {
+ if (side === 'input') {
+ const input = this.tx.vin[index];
+ if (input && input.txid && input.vout != null) {
+ this.router.navigate([this.relativeUrlPipe.transform('/tx'), input.txid + ':' + input.vout], {
+ queryParamsHandling: 'merge',
+ fragment: 'flow'
+ });
+ } else {
+ this.selectInput.emit(index);
+ }
+ } else {
+ const output = this.tx.vout[index];
+ const outspend = this.outspends[index];
+ if (output && outspend && outspend.spent && outspend.txid) {
+ this.router.navigate([this.relativeUrlPipe.transform('/tx'), outspend.vin + ':' + outspend.txid], {
+ queryParamsHandling: 'merge',
+ fragment: 'flow'
+ });
+ } else {
+ this.selectOutput.emit(index);
+ }
+ }
+ }
}
diff --git a/frontend/src/app/services/state.service.ts b/frontend/src/app/services/state.service.ts
index 920f32dd9..9f7cc58a3 100644
--- a/frontend/src/app/services/state.service.ts
+++ b/frontend/src/app/services/state.service.ts
@@ -1,4 +1,4 @@
-import { Inject, Injectable, PLATFORM_ID } from '@angular/core';
+import { Inject, Injectable, PLATFORM_ID, LOCALE_ID } from '@angular/core';
import { ReplaySubject, BehaviorSubject, Subject, fromEvent, Observable } from 'rxjs';
import { Transaction } from '../interfaces/electrs.interface';
import { IBackendInfo, MempoolBlock, MempoolBlockWithTransactions, MempoolBlockDelta, MempoolInfo, Recommendedfees, ReplacedTransaction, TransactionStripped } from '../interfaces/websocket.interface';
@@ -113,6 +113,7 @@ export class StateService {
constructor(
@Inject(PLATFORM_ID) private platformId: any,
+ @Inject(LOCALE_ID) private locale: string,
private router: Router,
private storageService: StorageService,
) {
@@ -151,7 +152,10 @@ export class StateService {
this.blockVSize = this.env.BLOCK_WEIGHT_UNITS / 4;
- this.timeLtr = new BehaviorSubject(this.storageService.getValue('time-preference-ltr') === 'true');
+ const savedTimePreference = this.storageService.getValue('time-preference-ltr');
+ const rtlLanguage = (this.locale.startsWith('ar') || this.locale.startsWith('fa') || this.locale.startsWith('he'));
+ // default time direction is right-to-left, unless locale is a RTL language
+ this.timeLtr = new BehaviorSubject(savedTimePreference === 'true' || (savedTimePreference == null && rtlLanguage));
this.timeLtr.subscribe((ltr) => {
this.storageService.setValue('time-preference-ltr', ltr ? 'true' : 'false');
});