1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00

Merge pull request #3743 from hadicharara/hadicharara/added-support-for-rtl-layouts

Add Initial support for RTL layouts
This commit is contained in:
Bill Thornton 2022-10-15 02:50:48 -04:00 committed by GitHub
commit 84c007fa0b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
82 changed files with 1163 additions and 242 deletions

View file

@ -117,10 +117,17 @@ div[data-role=controlgroup] a[data-role=button]:last-child {
} }
div[data-role=controlgroup] a[data-role=button] + a[data-role=button] { div[data-role=controlgroup] a[data-role=button] + a[data-role=button] {
[dir="ltr"] & {
border-left-width: 0 !important; border-left-width: 0 !important;
margin: 0 0 0 -0.4em !important; margin: 0 0 0 -0.4em !important;
} }
[dir="rtl"] & {
border-right-width: 0 !important;
margin: 0 -0.4em 0 0 !important;
}
}
div[data-role=controlgroup] a.ui-btn-active { div[data-role=controlgroup] a.ui-btn-active {
background: #00a4dc !important; background: #00a4dc !important;
color: #292929 !important; color: #292929 !important;

View file

@ -32,6 +32,39 @@
} }
} }
@mixin header-poster-padding-rtl() {
padding-left: unset;
padding-right: 37.5%;
@media all and (min-width: 43.75em) {
padding-right: 25%;
}
@media all and (min-width: 50em) {
padding-right: 20%;
}
@media all and (min-width: 75em) {
padding-right: 16.666666666666666666666666666667%;
}
@media all and (min-width: 87.5em) {
padding-right: 14.285714285714285714285714285714%;
}
@media all and (min-width: 100em) {
padding-right: 12.5%;
}
@media all and (min-width: 120em) {
padding-right: 11.111111111111111111111111111111%;
}
@media all and (min-width: 131.25em) {
padding-right: 10%;
}
}
.headerUserImage, .headerUserImage,
.navMenuOption, .navMenuOption,
.pageTitle { .pageTitle {
@ -137,10 +170,17 @@
.pageTitle { .pageTitle {
display: inline-flex; display: inline-flex;
margin: 0 0 0 0.5em;
height: 1.7em; height: 1.7em;
align-items: center; align-items: center;
flex-shrink: 1; flex-shrink: 1;
[dir="ltr"] & {
margin: 0 0 0 0.5em;
}
[dir="rtl"] & {
margin: 0 0.5em 0 0;
}
} }
.pageTitleWithDefaultLogo { .pageTitleWithDefaultLogo {
@ -162,6 +202,10 @@
background-size: contain; background-size: contain;
background-repeat: no-repeat; background-repeat: no-repeat;
width: 13.2em; width: 13.2em;
[dir='rtl'] & {
background-position: right center;
}
} }
.skinHeader { .skinHeader {
@ -211,16 +255,30 @@
align-items: center; align-items: center;
text-decoration: none; text-decoration: none;
color: inherit; color: inherit;
padding: 0.9em 0 0.9em 2.4em !important;
flex-grow: 1; flex-grow: 1;
font-weight: 400 !important; font-weight: 400 !important;
margin: 0 !important; margin: 0 !important;
border-radius: 0 !important; border-radius: 0 !important;
[dir="ltr"] & {
padding: 0.9em 0 0.9em 2.4em !important;
}
[dir="rtl"] & {
padding: 0.9em 2.4em 0.9em 0 !important;
}
} }
.navMenuOptionIcon { .navMenuOptionIcon {
margin-right: 1.2em;
flex-shrink: 0; flex-shrink: 0;
[dir="ltr"] & {
margin-right: 1.2em;
}
[dir="rtl"] & {
margin-left: 1.2em;
}
} }
.navMenuOptionText { .navMenuOptionText {
@ -229,8 +287,15 @@
} }
.sidebarHeader { .sidebarHeader {
padding-left: 1.2em;
margin: 1em 0 0.5em; margin: 1em 0 0.5em;
[dir="ltr"] & {
padding-left: 1.2em;
}
[dir="rtl"] & {
padding-right: 1.2em;
}
} }
.dashboardDocument .skinBody { .dashboardDocument .skinBody {
@ -240,6 +305,10 @@
right: 0; right: 0;
bottom: 0; bottom: 0;
left: 0; left: 0;
[dir="rtl"] & {
transition: right ease-in-out 0.3s, padding ease-in-out 0.3s;
}
} }
.centerMessage { .centerMessage {
@ -267,6 +336,14 @@
box-shadow: none !important; box-shadow: none !important;
width: 20.205em !important; width: 20.205em !important;
font-size: 94%; font-size: 94%;
[dir="ltr"] & {
left: 0 !important;
}
[dir="rtl"] & {
right: 0 !important;
}
} }
.dashboardDocument .mainDrawer-scrollContainer { .dashboardDocument .mainDrawer-scrollContainer {
@ -274,8 +351,14 @@
} }
.dashboardDocument .skinBody { .dashboardDocument .skinBody {
[dir="ltr"] & {
left: 20em; left: 20em;
} }
[dir="rtl"] & {
right: 20em;
}
}
} }
@media all and (max-width: 100em) { @media all and (max-width: 100em) {
@ -340,9 +423,16 @@
.headerArrowImage { .headerArrowImage {
height: 20px; height: 20px;
[dir="ltr"] & {
margin-left: 0.5em; margin-left: 0.5em;
} }
[dir="rtl"] & {
margin-right: 0.5em;
}
}
.backdropContainer { .backdropContainer {
position: fixed; position: fixed;
top: 0; top: 0;
@ -370,15 +460,28 @@
} }
.viewControls + .listTopPaging { .viewControls + .listTopPaging {
[dir="ltr"] & {
margin-left: 0.5em !important; margin-left: 0.5em !important;
} }
[dir="rtl"] & {
margin-right: 0.5em !important;
}
}
.criticReview { .criticReview {
margin: 1.5em 0; margin: 1.5em 0;
background: #222; background: #222;
padding: 0.8em 0.8em 0.8em 3em;
border-radius: 0.3em; border-radius: 0.3em;
position: relative; position: relative;
[dir="ltr"] & {
padding: 0.8em 0.8em 0.8em 3em;
}
[dir="rtl"] & {
padding: 0.8em 3em 0.8em 0.8em;
}
} }
.detailLogo { .detailLogo {
@ -431,14 +534,27 @@
} }
.reviewDate { .reviewDate {
[dir="ltr"] & {
margin-left: 1em; margin-left: 1em;
} }
[dir="rtl"] & {
margin-right: 1em;
}
}
.reviewScore { .reviewScore {
position: absolute; position: absolute;
[dir="ltr"] & {
left: 0.8em; left: 0.8em;
} }
[dir="rtl"] & {
right: 0.8em;
}
}
.itemBackdrop { .itemBackdrop {
background-size: cover; background-size: cover;
background-repeat: no-repeat; background-repeat: no-repeat;
@ -473,8 +589,6 @@
.detailPageContent { .detailPageContent {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding-left: 32.45vw;
padding-right: 2%;
.layout-mobile & { .layout-mobile & {
padding-left: 5%; padding-left: 5%;
@ -484,8 +598,24 @@
.layout-desktop &, .layout-desktop &,
.layout-tv & { .layout-tv & {
.emby-scroller { .emby-scroller {
[dir="ltr"] & {
margin-left: 0; margin-left: 0;
} }
[dir="rtl"] & {
margin-right: 0;
}
}
}
[dir="ltr"] & {
padding-left: 32.45vw;
padding-right: 2%;
}
[dir="rtl"] & {
padding-right: 32.45vw;
padding-left: 2%;
} }
} }
@ -613,7 +743,6 @@
.layout-mobile .mainDetailButtons { .layout-mobile .mainDetailButtons {
margin-top: 1em; margin-top: 1em;
margin-bottom: 0.5em; margin-bottom: 0.5em;
margin-left: 0;
@include header-poster-padding; @include header-poster-padding;
@ -622,6 +751,21 @@
margin-bottom: 0; margin-bottom: 0;
padding-left: 0; padding-left: 0;
} }
[dir="ltr"] & {
margin-left: 0;
}
[dir="rtl"] & {
margin-right: 0;
padding-left: unset !important;
@include header-poster-padding-rtl;
@media all and (max-width: 32em) {
padding-right: 0 !important;
}
}
} }
.subtitle { .subtitle {
@ -651,15 +795,21 @@
.layout-desktop & { .layout-desktop & {
position: relative; position: relative;
padding-left: 32.45vw;
} }
.layout-tv & { .layout-tv & {
display: block; display: block;
padding-left: 32.45vw;
} }
} }
.layout-desktop [dir="rtl"] .detailPagePrimaryContainer {
padding-right: 32.45vw;
}
.layout-desktop [dir="ltr"] .detailPagePrimaryContainer {
padding-left: 32.45vw;
}
.layout-desktop .detailRibbon { .layout-desktop .detailRibbon {
margin-top: -7.2em; margin-top: -7.2em;
height: 7.2em; height: 7.2em;
@ -676,7 +826,13 @@
flex: 1 0 0; flex: 1 0 0;
.layout-mobile & { .layout-mobile & {
[dir="ltr"] {
@include header-poster-padding; @include header-poster-padding;
}
[dir="rtl"] & {
@include header-poster-padding-rtl;
}
@media all and (max-width: 32em) { @media all and (max-width: 32em) {
position: relative; position: relative;
@ -685,9 +841,16 @@
} }
.infoText { .infoText {
text-align: left;
min-width: 0; min-width: 0;
max-width: 100%; max-width: 100%;
[dir="ltr"] & {
text-align: left;
}
[dir="rtl"] & {
text-align: right;
}
} }
.detailPageSecondaryContainer { .detailPageSecondaryContainer {
@ -749,6 +912,19 @@
width: 25vw; width: 25vw;
transform: translateY(-50%); transform: translateY(-50%);
} }
[dir="rtl"] & {
left: unset;
.layout-mobile &,
.layout-tv & {
right: 5%;
}
.layout-desktop & {
right: 3.3%;
}
}
} }
.detailPagePrimaryContent { .detailPagePrimaryContent {
@ -768,6 +944,10 @@
.itemDetailImage { .itemDetailImage {
width: 100% !important; width: 100% !important;
box-shadow: 0 0.1em 0.5em 0 rgba(0, 0, 0, 0.75); box-shadow: 0 0.1em 0.5em 0 rgba(0, 0, 0, 0.75);
[dir="rtl"] & {
box-shadow: 0 0 0.5em 0.1em rgba(0, 0, 0, 0.75);
}
} }
div.itemDetailGalleryLink.defaultCardBackground { div.itemDetailGalleryLink.defaultCardBackground {
@ -871,9 +1051,17 @@ div.itemDetailGalleryLink.defaultCardBackground {
} }
.recordingFields button { .recordingFields button {
flex-shrink: 0;
[dir="ltr"] & {
margin-left: 0; margin-left: 0;
margin-right: 0.5em; margin-right: 0.5em;
flex-shrink: 0; }
[dir="rtl"] & {
margin-right: 0;
margin-left: 0.5em;
}
} }
.mainDetailButtons.hide + .recordingFields { .mainDetailButtons.hide + .recordingFields {
@ -1016,8 +1204,15 @@ div.itemDetailGalleryLink.defaultCardBackground {
} }
.mediaInfoLabel { .mediaInfoLabel {
margin-right: 1em;
font-weight: 600; font-weight: 600;
[dir="ltr"] & {
margin-right: 1em;
}
[dir="rtl"] & {
margin-left: 1em;
}
} }
.recordingProgressBar::-moz-progress-bar { .recordingProgressBar::-moz-progress-bar {
@ -1107,19 +1302,39 @@ div:not(.sectionTitleContainer-cards) > .sectionTitle-cards {
} }
.sectionTitleButton { .sectionTitleButton {
margin-left: 1.5em !important;
flex-shrink: 0; flex-shrink: 0;
[dir="ltr"] & {
margin-left: 1.5em !important;
}
[dir="rtl"] & {
margin-right: 1.5em !important;
}
} }
.sectionTitleButton + .sectionTitleButton { .sectionTitleButton + .sectionTitleButton {
[dir="ltr"] & {
margin-left: 0.5em !important; margin-left: 0.5em !important;
} }
[dir="rtl"] & {
margin-right: 0.5em !important;
}
}
.sectionTitleIconButton { .sectionTitleIconButton {
margin-left: 1.5em !important;
flex-shrink: 0; flex-shrink: 0;
font-size: 84% !important; font-size: 84% !important;
padding: 0.5em !important; padding: 0.5em !important;
[dir="ltr"] & {
margin-left: 1.5em !important;
}
[dir="rtl"] & {
margin-right: 1.5em !important;
}
} }
.horizontalItemsContainer { .horizontalItemsContainer {
@ -1148,14 +1363,28 @@ div:not(.sectionTitleContainer-cards) > .sectionTitle-cards {
} }
.padded-left { .padded-left {
[dir="ltr"] & {
padding-left: 3.3%; padding-left: 3.3%;
padding-left: max(env(safe-area-inset-left), 3.3%); padding-left: max(env(safe-area-inset-left), 3.3%);
} }
.padded-right { [dir="rtl"] & {
padding-right: 3.3%; padding-right: 3.3%;
padding-right: max(env(safe-area-inset-right), 3.3%); padding-right: max(env(safe-area-inset-right), 3.3%);
} }
}
.padded-right {
[dir="ltr"] & {
padding-right: 3.3%;
padding-right: max(env(safe-area-inset-right), 3.3%);
}
[dir="rtl"] & {
padding-left: 3.3%;
padding-left: max(env(safe-area-inset-left), 3.3%);
}
}
.padded-top { .padded-top {
padding-top: 1em; padding-top: 1em;

View file

@ -7,9 +7,15 @@
} }
.libraryTree { .libraryTree {
[dir="ltr"] & {
margin-left: 0.25em; margin-left: 0.25em;
} }
[dir="rtl"] & {
margin-right: 0.25em;
}
}
.offlineEditorNode { .offlineEditorNode {
color: #c33; color: #c33;
} }
@ -56,15 +62,30 @@
position: fixed; position: fixed;
top: 5.2em; top: 5.2em;
bottom: 0; bottom: 0;
left: 0;
width: 30%; width: 30%;
border-right: 1px solid #555;
display: block; display: block;
[dir="ltr"] & {
left: 0;
border-right: 1px solid #555;
}
[dir="rtl"] & {
right: 0;
border-left: 1px solid #555;
}
} }
.editPageInnerContent { .editPageInnerContent {
float: right;
width: 68.5%; width: 68.5%;
[dir="ltr"] & {
float: right;
}
[dir="rtl"] & {
float: left;
}
} }
} }

View file

@ -125,6 +125,10 @@
-webkit-box-align: center; -webkit-box-align: center;
-webkit-align-items: center; -webkit-align-items: center;
align-items: center; align-items: center;
[dir="rtl"] & {
flex-direction: row-reverse;
}
} }
.osdVolumeSliderContainer { .osdVolumeSliderContainer {

View file

@ -45,9 +45,15 @@
} }
.actionsheetListItemBody { .actionsheetListItemBody {
[dir="ltr"] & {
padding: 0.4em 1em 0.4em 0.6em !important; padding: 0.4em 1em 0.4em 0.6em !important;
} }
[dir="rtl"] & {
padding: 0.4em 0.6em 0.4em 1em !important;
}
}
.actionSheetItemText { .actionSheetItemText {
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
@ -64,10 +70,18 @@
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
flex-shrink: 0; flex-shrink: 0;
[dir="ltr"] & {
margin-left: 5em; margin-left: 5em;
margin-right: 0.5em; margin-right: 0.5em;
} }
[dir="rtl"] & {
margin-right: 5em;
margin-left: 0.5em;
}
}
.actionSheetScroller { .actionSheetScroller {
/* Override default style being applied by polymer */ /* Override default style being applied by polymer */
margin-bottom: 0 !important; margin-bottom: 0 !important;
@ -101,8 +115,15 @@
} }
.actionsheetMenuItemIcon { .actionsheetMenuItemIcon {
margin: 0 0.85em 0 0.45em !important;
padding: 0 !important; padding: 0 !important;
[dir="ltr"] & {
margin: 0 0.85em 0 0.45em !important;
}
[dir="rtl"] & {
margin: 0 0.45em 0 0.85em !important;
}
} }
.actionsheet-xlargeFont { .actionsheet-xlargeFont {
@ -112,5 +133,12 @@
.btnCloseActionSheet { .btnCloseActionSheet {
position: fixed; position: fixed;
top: 0.75em; top: 0.75em;
[dir="ltr"] & {
left: 0.5em; left: 0.5em;
} }
[dir="rtl"] & {
right: 0.5em;
}
}

View file

@ -110,15 +110,29 @@
} }
.alphaPicker-fixed-right { .alphaPicker-fixed-right {
[dir="ltr"] & {
right: 0.4em; right: 0.4em;
right: max(env(safe-area-inset-right), 0.4em); right: max(env(safe-area-inset-right), 0.4em);
} }
[dir="rtl"] & {
left: 0.4em;
left: max(env(safe-area-inset-left), 0.4em);
}
}
@media all and (min-width: 62.5em) { @media all and (min-width: 62.5em) {
.alphaPicker-fixed-right { .alphaPicker-fixed-right {
[dir="ltr"] & {
right: 1em; right: 1em;
right: max(env(safe-area-inset-right), 1em); right: max(env(safe-area-inset-right), 1em);
} }
[dir="rtl"] & {
left: 1em;
left: max(env(safe-area-inset-left), 1em);
}
}
} }
@media all and (max-height: 31.25em) { @media all and (max-height: 31.25em) {

View file

@ -302,9 +302,16 @@ button::-moz-focus-inner {
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
[dir="ltr"] & {
text-align: left; text-align: left;
} }
[dir="rtl"] & {
text-align: right;
}
}
.dialog .cardText { .dialog .cardText {
text-overflow: initial; text-overflow: initial;
} }
@ -364,7 +371,7 @@ button::-moz-focus-inner {
.cardTextCentered, .cardTextCentered,
.cardTextCentered > .textActionButton { .cardTextCentered > .textActionButton {
text-align: center; text-align: center !important;
} }
.cardText-rightmargin { .cardText-rightmargin {
@ -396,21 +403,35 @@ button::-moz-focus-inner {
} }
.cardIndicators { .cardIndicators {
right: 0.225em;
top: 0.225em; top: 0.225em;
position: absolute; position: absolute;
display: flex; display: flex;
align-items: center; align-items: center;
contain: layout style; contain: layout style;
[dir="ltr"] & {
right: 0.225em;
}
[dir="rtl"] & {
left: 0.225em;
}
} }
.cardProgramAttributeIndicators { .cardProgramAttributeIndicators {
top: 0; top: 0;
left: 0;
position: absolute; position: absolute;
display: flex; display: flex;
text-transform: uppercase; text-transform: uppercase;
font-size: 92%; font-size: 92%;
[dir="ltr"] & {
left: 0;
}
[dir="rtl"] & {
right: 0;
}
} }
.programAttributeIndicator { .programAttributeIndicator {
@ -430,9 +451,16 @@ button::-moz-focus-inner {
.cardOverlayButton-br { .cardOverlayButton-br {
position: absolute; position: absolute;
bottom: 0; bottom: 0;
[dir="ltr"] & {
right: 0; right: 0;
} }
[dir="rtl"] & {
left: 0;
}
}
.cardOverlayButtonIcon { .cardOverlayButtonIcon {
background-color: rgba(0, 0, 0, 0.7) !important; background-color: rgba(0, 0, 0, 0.7) !important;
border-radius: 100em; border-radius: 100em;

View file

@ -705,7 +705,7 @@ import { appRouter } from '../appRouter';
if (text) { if (text) {
html += "<div class='" + currentCssClass + "'>"; html += "<div class='" + currentCssClass + "'>";
html += text; html += '<bdi>' + text + '</bdi>';
html += '</div>'; html += '</div>';
valid++; valid++;
@ -908,19 +908,20 @@ import { appRouter } from '../appRouter';
} }
if (options.showYear || options.showSeriesYear) { if (options.showYear || options.showSeriesYear) {
const productionYear = item.ProductionYear && datetime.toLocaleString(item.ProductionYear, {useGrouping: false});
if (item.Type === 'Series') { if (item.Type === 'Series') {
if (item.Status === 'Continuing') { if (item.Status === 'Continuing') {
lines.push(globalize.translate('SeriesYearToPresent', item.ProductionYear || '')); lines.push(globalize.translate('SeriesYearToPresent', productionYear || ''));
} else { } else {
if (item.EndDate && item.ProductionYear) { if (item.EndDate && item.ProductionYear) {
const endYear = datetime.parseISO8601Date(item.EndDate).getFullYear(); const endYear = datetime.toLocaleString(datetime.parseISO8601Date(item.EndDate).getFullYear(), {useGrouping: false});
lines.push(item.ProductionYear + ((endYear === item.ProductionYear) ? '' : (' - ' + endYear))); lines.push(productionYear + ((endYear === item.ProductionYear) ? '' : (' - ' + endYear)));
} else { } else {
lines.push(item.ProductionYear || ''); lines.push(productionYear || '');
} }
} }
} else { } else {
lines.push(item.ProductionYear || ''); lines.push(productionYear || '');
} }
} }

View file

@ -1,5 +1,5 @@
<div class="formDialogHeader formDialogHeader-clear justify-content-center"> <div class="formDialogHeader formDialogHeader-clear justify-content-center">
<h1 class="formDialogHeaderTitle" style="margin-left:0;margin-top: .5em;padding: 0 1em;"></h1> <h1 class="formDialogHeaderTitle" style="margin-top: .5em;padding: 0 1em;"></h1>
</div> </div>
<div class="formDialogContent smoothScrollY"> <div class="formDialogContent smoothScrollY">

View file

@ -12,17 +12,29 @@
} }
.formDialogHeaderTitle { .formDialogHeaderTitle {
margin-left: 0.25em;
/* In case of h1, h2, h3 */ /* In case of h1, h2, h3 */
margin-top: 0; margin-top: 0;
margin-bottom: 0; margin-bottom: 0;
[dir="ltr"] & {
margin-left: 0.25em;
}
[dir="rtl"] & {
margin-right: 0.25em;
}
} }
.formDialogHeaderTitle:first-child { .formDialogHeaderTitle:first-child {
[dir="ltr"] & {
margin-left: 1em; margin-left: 1em;
} }
[dir="rtl"] & {
margin-right: 1em;
}
}
.formDialogContent:not(.no-grow) { .formDialogContent:not(.no-grow) {
flex-grow: 1; flex-grow: 1;
} }
@ -98,8 +110,8 @@
.formDialogFooterItem-autosize { .formDialogFooterItem-autosize {
flex-basis: initial; flex-basis: initial;
flex-grow: initial; flex-grow: initial;
padding-left: 2em;
padding-right: 2em; padding-right: 2em;
padding-left: 2em;
} }
@media all and (min-width: 50em) { @media all and (min-width: 50em) {

View file

@ -307,13 +307,20 @@
} }
.programIcon { .programIcon {
margin-left: 0.5em;
height: 1em; height: 1em;
width: 1em; width: 1em;
font-size: 1.6em; font-size: 1.6em;
color: #ddd; color: #ddd;
flex-shrink: 0; flex-shrink: 0;
flex-grow: 0; flex-grow: 0;
[dir="ltr"] & {
margin-left: 0.5em;
}
[dir="rtl"] & {
margin-right: 0.5em;
}
} }
.guide-programTextIcon { .guide-programTextIcon {
@ -340,11 +347,19 @@
} }
.guideChannelName { .guideChannelName {
margin-left: auto;
margin-right: 1em;
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden; overflow: hidden;
max-width: 70%; max-width: 70%;
[dir="ltr"] & {
margin-left: auto;
margin-right: 1em;
}
[dir="rtl"] & {
margin-right: auto;
margin-left: 1em;
}
} }
.guideChannelImage { .guideChannelImage {

View file

@ -13,7 +13,7 @@
<div class="flex align-items-center" style="margin:1.5em 0;"> <div class="flex align-items-center" style="margin:1.5em 0;">
<h2 style="margin:0;">${HeaderAddUpdateImage}</h2> <h2 style="margin:0;">${HeaderAddUpdateImage}</h2>
<button is="emby-button" type="button" class="raised raised-mini btnBrowse" style="margin-left:1.5em;"> <button is="emby-button" type="button" class="raised raised-mini btnBrowse">
<span class="material-icons folder" aria-hidden="true"></span> <span class="material-icons folder" aria-hidden="true"></span>
<span>${Browse}</span> <span>${Browse}</span>
</button> </button>

View file

@ -10,3 +10,7 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.raised.raised-mini.btnBrowse {
margin-left: 1.5em;
}

View file

@ -7,3 +7,23 @@
.first-imageEditor-buttons { .first-imageEditor-buttons {
margin-top: 2em; margin-top: 2em;
} }
.btnBrowseAllImages {
[dir="ltr"] & {
margin-left: 1em;
}
[dir="rtl"] & {
margin-right: 1em;
}
}
.btnOpenUploadMenu {
[dir="ltr"] & {
margin-left: 0.5em;
}
[dir="rtl"] & {
margin-right: 0.5em;
}
}

View file

@ -11,10 +11,10 @@
<div id="imagesContainer"> <div id="imagesContainer">
<div class="imageEditor-buttons first-imageEditor-buttons"> <div class="imageEditor-buttons first-imageEditor-buttons">
<h2 style="margin:0;">${Images}</h2> <h2 style="margin:0;">${Images}</h2>
<button type="button" is="emby-button" class="btnBrowseAllImages fab mini autoSize" style="margin-left: 1em;"> <button type="button" is="emby-button" class="btnBrowseAllImages fab mini autoSize">
<span class="material-icons search" aria-hidden="true"></span> <span class="material-icons search" aria-hidden="true"></span>
</button> </button>
<button type="button" is="emby-button" class="btnOpenUploadMenu fab mini hide" style="margin-left: .5em;"> <button type="button" is="emby-button" class="btnOpenUploadMenu fab mini hide">
<span class="material-icons add" aria-hidden="true"></span> <span class="material-icons add" aria-hidden="true"></span>
</button> </button>
</div> </div>
@ -26,10 +26,10 @@
<div id="backdropsContainer" class="hide"> <div id="backdropsContainer" class="hide">
<div class="imageEditor-buttons"> <div class="imageEditor-buttons">
<h2 style="margin:0;">${Backdrops}</h2> <h2 style="margin:0;">${Backdrops}</h2>
<button type="button" is="emby-button" class="btnBrowseAllImages fab mini autoSize" style="margin-left: 1em;" data-imagetype="Backdrop"> <button type="button" is="emby-button" class="btnBrowseAllImages fab mini autoSize" data-imagetype="Backdrop">
<span class="material-icons search" aria-hidden="true"></span> <span class="material-icons search" aria-hidden="true"></span>
</button> </button>
<button type="button" is="emby-button" class="btnOpenUploadMenu fab mini hide" style="margin-left: .5em;" data-imagetype="Backdrop"> <button type="button" is="emby-button" class="btnOpenUploadMenu fab mini hide" data-imagetype="Backdrop">
<span class="material-icons add" aria-hidden="true"></span> <span class="material-icons add" aria-hidden="true"></span>
</button> </button>
</div> </div>

View file

@ -78,7 +78,7 @@ export function getPlayedIndicatorHtml(item) {
if (enablePlayedIndicator(item)) { if (enablePlayedIndicator(item)) {
const userData = item.UserData || {}; const userData = item.UserData || {};
if (userData.UnplayedItemCount) { if (userData.UnplayedItemCount) {
return '<div class="countIndicator indicator">' + userData.UnplayedItemCount + '</div>'; return '<div class="countIndicator indicator">' + datetime.toLocaleString(userData.UnplayedItemCount) + '</div>';
} }
if (userData.PlayedPercentage && userData.PlayedPercentage >= 100 || (userData.Played)) { if (userData.PlayedPercentage && userData.PlayedPercentage >= 100 || (userData.Played)) {
@ -93,7 +93,7 @@ export function getChildCountIndicatorHtml(item, options) {
const minCount = options && options.minCount ? options.minCount : 0; const minCount = options && options.minCount ? options.minCount : 0;
if (item.ChildCount && item.ChildCount > minCount) { if (item.ChildCount && item.ChildCount > minCount) {
return '<div class="countIndicator indicator">' + item.ChildCount + '</div>'; return '<div class="countIndicator indicator">' + datetime.toLocaleString(item.ChildCount) + '</div>';
} }
return ''; return '';

View file

@ -8,8 +8,15 @@
.itemProgressBarForeground { .itemProgressBarForeground {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0;
bottom: 0; bottom: 0;
[dir="ltr"] & {
left: 0;
}
[dir="rtl"] & {
right: 0;
}
} }
.indicator { .indicator {
@ -32,9 +39,15 @@
} }
.indicator + .indicator { .indicator + .indicator {
[dir="ltr"] & {
margin-left: 0.25em; margin-left: 0.25em;
} }
[dir="rtl"] & {
margin-right: 0.25em;
}
}
.indicatorIcon { .indicatorIcon {
width: auto; width: auto;
height: auto; height: auto;

View file

@ -66,7 +66,7 @@ const attributeDelimiterHtml = layoutManager.tv ? '' : '<span class="hide">: </s
html += `${createAttribute(globalize.translate('MediaInfoFormat'), version.Formats.join(','))}<br/>`; html += `${createAttribute(globalize.translate('MediaInfoFormat'), version.Formats.join(','))}<br/>`;
} }
if (version.Path && user && user.Policy.IsAdministrator) { if (version.Path && user && user.Policy.IsAdministrator) {
html += `${createAttribute(globalize.translate('MediaInfoPath'), version.Path)}<br/>`; html += `${createAttribute(globalize.translate('MediaInfoPath'), version.Path, true)}<br/>`;
} }
if (version.Size) { if (version.Size) {
const size = `${(version.Size / (1024 * 1024)).toFixed(0)} MB`; const size = `${(version.Size / (1024 * 1024)).toFixed(0)} MB`;
@ -212,8 +212,9 @@ const attributeDelimiterHtml = layoutManager.tv ? '' : '<span class="hide">: </s
return html; return html;
} }
function createAttribute(label, value) { // File Paths should be always ltr. The isLtr parameter allows this.
return `<span class="mediaInfoLabel">${label}</span>${attributeDelimiterHtml}<span class="mediaInfoAttribute">${escapeHtml(value)}</span>\n`; function createAttribute(label, value, isLtr) {
return `<span class="mediaInfoLabel">${label}</span>${attributeDelimiterHtml}<span class="mediaInfoAttribute" ${isLtr && 'dir="ltr"'}>${escapeHtml(value)}</span>\n`;
} }
function loadMediaInfo(itemId, serverId) { function loadMediaInfo(itemId, serverId) {

View file

@ -22,6 +22,7 @@ import '../cardbuilder/card.scss';
import ServerConnections from '../ServerConnections'; import ServerConnections from '../ServerConnections';
import toast from '../toast/toast'; import toast from '../toast/toast';
import template from './itemidentifier.template.html'; import template from './itemidentifier.template.html';
import datetime from '../../scripts/datetime';
const enableFocusTransform = !browser.slow && !browser.edge; const enableFocusTransform = !browser.slow && !browser.edge;
@ -166,7 +167,7 @@ import template from './itemidentifier.template.html';
lines.push(escapeHtml(identifyResult.Name)); lines.push(escapeHtml(identifyResult.Name));
if (identifyResult.ProductionYear) { if (identifyResult.ProductionYear) {
lines.push(identifyResult.ProductionYear); lines.push(datetime.toLocaleString(identifyResult.ProductionYear, {useGrouping: false}));
} }
let resultHtml = lines.join('<br/>'); let resultHtml = lines.join('<br/>');

View file

@ -11,6 +11,7 @@ import dom from '../../scripts/dom';
import '../../elements/emby-checkbox/emby-checkbox'; import '../../elements/emby-checkbox/emby-checkbox';
import '../../elements/emby-select/emby-select'; import '../../elements/emby-select/emby-select';
import '../../elements/emby-input/emby-input'; import '../../elements/emby-input/emby-input';
import './style.scss';
import template from './libraryoptionseditor.template.html'; import template from './libraryoptionseditor.template.html';
function populateLanguages(parent) { function populateLanguages(parent) {
@ -231,7 +232,7 @@ import template from './libraryoptionseditor.template.html';
html += '<h3 class="checkboxListLabel" style="margin:0;">' + globalize.translate('HeaderTypeImageFetchers', globalize.translate('TypeOptionPlural' + availableTypeOptions.Type)) + '</h3>'; html += '<h3 class="checkboxListLabel" style="margin:0;">' + globalize.translate('HeaderTypeImageFetchers', globalize.translate('TypeOptionPlural' + availableTypeOptions.Type)) + '</h3>';
const supportedImageTypes = availableTypeOptions.SupportedImageTypes || []; const supportedImageTypes = availableTypeOptions.SupportedImageTypes || [];
if (supportedImageTypes.length > 1 || supportedImageTypes.length === 1 && supportedImageTypes[0] !== 'Primary') { if (supportedImageTypes.length > 1 || supportedImageTypes.length === 1 && supportedImageTypes[0] !== 'Primary') {
html += '<button is="emby-button" class="raised btnImageOptionsForType" type="button" style="margin-left:1.5em;font-size:90%;"><span>' + globalize.translate('HeaderFetcherSettings') + '</span></button>'; html += '<button is="emby-button" class="raised btnImageOptionsForType" type="button" style="font-size:90%;"><span>' + globalize.translate('HeaderFetcherSettings') + '</span></button>';
} }
html += '</div>'; html += '</div>';
html += '<div class="checkboxList paperList checkboxList-paperList">'; html += '<div class="checkboxList paperList checkboxList-paperList">';

View file

@ -0,0 +1,9 @@
.raised.btnImageOptionsForType {
[dir="ltr"] & {
margin-left: 1.5em;
}
[dir="rtl"] & {
margin-right: 1.5em;
}
}

View file

@ -149,7 +149,7 @@ import ServerConnections from '../ServerConnections';
elem.classList.add('listItemBodyText'); elem.classList.add('listItemBodyText');
elem.innerText = text; elem.innerHTML = '<bdi>' + text + '</bdi>';
html += elem.outerHTML; html += elem.outerHTML;
} }
@ -414,7 +414,7 @@ import ServerConnections from '../ServerConnections';
if (enableOverview && item.Overview) { if (enableOverview && item.Overview) {
html += '<div class="secondary listItem-overview listItemBodyText">'; html += '<div class="secondary listItem-overview listItemBodyText">';
html += item.Overview; html += '<bdi>' + item.Overview + '</bdi>';
html += '</div>'; html += '</div>';
} }
@ -477,7 +477,7 @@ import ServerConnections from '../ServerConnections';
if (enableOverview && item.Overview) { if (enableOverview && item.Overview) {
html += '<div class="listItem-bottomoverview secondary">'; html += '<div class="listItem-bottomoverview secondary">';
html += item.Overview; html += '<bdi>' + item.Overview + '</bdi>';
html += '</div>'; html += '</div>';
} }
} }

View file

@ -1,3 +1,10 @@
.listItem,
.listItemBody,
.listItemMediaInfo {
display: flex;
contain: layout style;
}
.listItem { .listItem {
background: transparent; background: transparent;
border: 0; border: 0;
@ -9,10 +16,18 @@
margin: 0; margin: 0;
display: block; display: block;
align-items: center; align-items: center;
text-align: left;
padding: 0.25em 0.25em 0.25em 0.5em;
cursor: pointer; cursor: pointer;
overflow: hidden; overflow: hidden;
[dir="ltr"] & {
text-align: left;
padding: 0.25em 0.25em 0.25em 0.5em;
}
[dir="rtl"] & {
text-align: right;
padding: 0.25em 0.5em 0.25em 0.25em;
}
} }
.listItem-withContentWrapper { .listItem-withContentWrapper {
@ -75,8 +90,15 @@
} }
.listViewDragHandle { .listViewDragHandle {
margin-left: -0.25em !important;
touch-action: none; touch-action: none;
[dir="ltr"] & {
margin-left: -0.25em !important;
}
[dir="rtl"] & {
margin-right: -0.25em !important;
}
} }
.listItemBody { .listItemBody {
@ -89,13 +111,6 @@
justify-content: center; justify-content: center;
} }
.listItem,
.listItemBody,
.listItemMediaInfo {
display: flex;
contain: layout style;
}
.layout-tv .listItemBody { .layout-tv .listItemBody {
padding: 0.35em 0.75em; padding: 0.35em 0.75em;
} }
@ -211,7 +226,14 @@
width: 1em !important; width: 1em !important;
height: 1em !important; height: 1em !important;
font-size: 143%; font-size: 143%;
padding: 0 0.25em 0 0;
[dir="ltr"] & {
margin: 0 0.25em 0 0;
}
[dir="rtl"] & {
margin: 0 0 0 0.25em;
}
} }
.listItemIcon:not(.listItemIcon-transparent) { .listItemIcon:not(.listItemIcon-transparent) {
@ -219,9 +241,16 @@
color: #fff; color: #fff;
padding: 0.5em; padding: 0.5em;
border-radius: 100em; border-radius: 100em;
[dir="ltr"] & {
margin: 0 0.2em 0 0.4em; margin: 0 0.2em 0 0.4em;
} }
[dir="rtl"] & {
margin: 0 0.4em 0 0.2em;
}
}
.listItemProgressBar { .listItemProgressBar {
position: absolute; position: absolute;
bottom: 0; bottom: 0;

View file

@ -13,6 +13,7 @@ export function show() {
if (!elem) { if (!elem) {
elem = document.createElement('div'); elem = document.createElement('div');
elem.setAttribute('dir', 'ltr');
loadingElem = elem; loadingElem = elem;
elem.classList.add('docspinner'); elem.classList.add('docspinner');

View file

@ -20,6 +20,7 @@ import '../../elements/emby-toggle/emby-toggle';
import '../listview/listview.scss'; import '../listview/listview.scss';
import '../formdialog.scss'; import '../formdialog.scss';
import '../../assets/css/flexstyles.scss'; import '../../assets/css/flexstyles.scss';
import './style.scss';
import toast from '../toast/toast'; import toast from '../toast/toast';
import alert from '../alert'; import alert from '../alert';
import template from './mediaLibraryCreator.template.html'; import template from './mediaLibraryCreator.template.html';
@ -119,12 +120,12 @@ import template from './mediaLibraryCreator.template.html';
function getFolderHtml(pathInfo, index) { function getFolderHtml(pathInfo, index) {
let html = ''; let html = '';
html += '<div class="listItem listItem-border lnkPath" style="padding-left:.5em;">'; html += '<div class="listItem listItem-border lnkPath">';
html += `<div class="${pathInfo.NetworkPath ? 'listItemBody two-line' : 'listItemBody'}">`; html += `<div class="${pathInfo.NetworkPath ? 'listItemBody two-line' : 'listItemBody'}">`;
html += `<div class="listItemBodyText">${escapeHtml(pathInfo.Path)}</div>`; html += `<div class="listItemBodyText" dir="ltr">${escapeHtml(pathInfo.Path)}</div>`;
if (pathInfo.NetworkPath) { if (pathInfo.NetworkPath) {
html += `<div class="listItemBodyText secondary">${escapeHtml(pathInfo.NetworkPath)}</div>`; html += `<div class="listItemBodyText secondary" dir="ltr">${escapeHtml(pathInfo.NetworkPath)}</div>`;
} }
html += '</div>'; html += '</div>';

View file

@ -19,7 +19,7 @@
<div class="folders"> <div class="folders">
<div style="display: flex; align-items: center;"> <div style="display: flex; align-items: center;">
<h1 style="margin: .5em 0;">${Folders}</h1> <h1 style="margin: .5em 0;">${Folders}</h1>
<button is="emby-button" type="button" class="fab btnAddFolder submit" style="margin-left:1em;" title="${Add}"> <button is="emby-button" type="button" class="fab btnAddFolder submit" title="${Add}">
<span class="material-icons add" aria-hidden="true"></span> <span class="material-icons add" aria-hidden="true"></span>
</button> </button>
</div> </div>

View file

@ -0,0 +1,19 @@
.fab.btnAddFolder.submit {
[dir="ltr"] & {
margin-left: 1em;
}
[dir="rtl"] & {
margin-right: 1em;
}
}
.listItem.listItem-border.lnkPath {
[dir="ltr"] & {
padding-left: 0.5em;
}
[dir="rtl"] & {
padding-right: 0.5em;
}
}

View file

@ -18,6 +18,7 @@ import '../../elements/emby-button/paper-icon-button-light';
import '../formdialog.scss'; import '../formdialog.scss';
import '../../elements/emby-toggle/emby-toggle'; import '../../elements/emby-toggle/emby-toggle';
import '../../assets/css/flexstyles.scss'; import '../../assets/css/flexstyles.scss';
import './style.scss';
import toast from '../toast/toast'; import toast from '../toast/toast';
import confirm from '../confirm/confirm'; import confirm from '../confirm/confirm';
import template from './mediaLibraryEditor.template.html'; import template from './mediaLibraryEditor.template.html';
@ -109,7 +110,7 @@ import template from './mediaLibraryEditor.template.html';
function getFolderHtml(pathInfo, index) { function getFolderHtml(pathInfo, index) {
let html = ''; let html = '';
html += `<div class="listItem listItem-border lnkPath" data-index="${index}" style="padding-left:.5em;">`; html += `<div class="listItem listItem-border lnkPath" data-index="${index}">`;
html += `<div class="${pathInfo.NetworkPath ? 'listItemBody two-line' : 'listItemBody'}">`; html += `<div class="${pathInfo.NetworkPath ? 'listItemBody two-line' : 'listItemBody'}">`;
html += '<h3 class="listItemBodyText">'; html += '<h3 class="listItemBodyText">';
html += escapeHtml(pathInfo.Path); html += escapeHtml(pathInfo.Path);

View file

@ -12,7 +12,7 @@
<div class="folders hide"> <div class="folders hide">
<div style="display: flex; align-items: center;"> <div style="display: flex; align-items: center;">
<h1 style="margin: .5em 0;">${Folders}</h1> <h1 style="margin: .5em 0;">${Folders}</h1>
<button is="emby-button" type="button" class="fab btnAddFolder submit" style="margin-left:1em;" title="${Add}"> <button is="emby-button" type="button" class="fab btnAddFolder submit" title="${Add}">
<span class="material-icons add" aria-hidden="true"></span> <span class="material-icons add" aria-hidden="true"></span>
</button> </button>
</div> </div>

View file

@ -0,0 +1,19 @@
.fab.btnAddFolder.submit {
[dir="ltr"] & {
margin-left: 1em;
}
[dir="rtl"] & {
margin-right: 1em;
}
}
.listItem.listItem-border.lnkPath {
[dir="ltr"] & {
padding-left: 0.5em;
}
[dir="rtl"] & {
padding-right: 0.5em;
}
}

View file

@ -176,16 +176,16 @@ import '../../elements/emby-button/emby-button';
if (options.year !== false && item.ProductionYear && item.Type === 'Series') { if (options.year !== false && item.ProductionYear && item.Type === 'Series') {
if (item.Status === 'Continuing') { if (item.Status === 'Continuing') {
miscInfo.push(globalize.translate('SeriesYearToPresent', item.ProductionYear)); miscInfo.push(globalize.translate('SeriesYearToPresent', datetime.toLocaleString(item.ProductionYear, {useGrouping: false})));
} else if (item.ProductionYear) { } else if (item.ProductionYear) {
text = item.ProductionYear; text = datetime.toLocaleString(item.ProductionYear, {useGrouping: false});
if (item.EndDate) { if (item.EndDate) {
try { try {
const endYear = datetime.parseISO8601Date(item.EndDate).getFullYear(); const endYear = datetime.toLocaleString(datetime.parseISO8601Date(item.EndDate).getFullYear(), {useGrouping: false});
if (endYear !== item.ProductionYear) { if (endYear !== item.ProductionYear) {
text += `-${datetime.parseISO8601Date(item.EndDate).getFullYear()}`; text += `-${endYear}`;
} }
} catch (e) { } catch (e) {
console.error('error parsing date:', item.EndDate); console.error('error parsing date:', item.EndDate);
@ -247,7 +247,7 @@ import '../../elements/emby-button/emby-button';
miscInfo.push(item.ProductionYear); miscInfo.push(item.ProductionYear);
} else if (item.PremiereDate) { } else if (item.PremiereDate) {
try { try {
text = datetime.parseISO8601Date(item.PremiereDate).getFullYear(); text = datetime.toLocaleString(datetime.parseISO8601Date(item.PremiereDate).getFullYear(), {useGrouping: false});
miscInfo.push(text); miscInfo.push(text);
} catch (e) { } catch (e) {
console.error('error parsing date:', item.PremiereDate); console.error('error parsing date:', item.PremiereDate);

View file

@ -1,6 +1,13 @@
.mediaInfoItem { .mediaInfoItem {
margin: 0 1em 0 0;
padding: 0; padding: 0;
[dir="ltr"] & {
margin: 0 1em 0 0;
}
[dir="rtl"] & {
margin: 0 0 0 1em;
}
} }
.mediaInfoText { .mediaInfoText {
@ -25,9 +32,15 @@
} }
.mediaInfoItem:last-child { .mediaInfoItem:last-child {
[dir="ltr"] & {
margin-right: 0; margin-right: 0;
} }
[dir="rtl"] & {
margin-left: 0;
}
}
.starRatingContainer { .starRatingContainer {
display: flex; display: flex;
align-items: center; align-items: center;
@ -46,13 +59,21 @@
} }
.mediaInfoCriticRating { .mediaInfoCriticRating {
padding-left: 1.5em;
background-position: left center;
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: auto 1.2em; background-size: auto 1.2em;
min-height: 1.2em; min-height: 1.2em;
display: flex; display: flex;
align-items: center; align-items: center;
[dir="ltr"] & {
padding-left: 1.5em;
background-position: left center;
}
[dir="rtl"] & {
padding-right: 1.5em;
background-position: right center;
}
} }
.mediaInfoCriticRatingFresh { .mediaInfoCriticRatingFresh {

View file

@ -17,6 +17,7 @@ import '../../elements/emby-button/paper-icon-button-light';
import '../formdialog.scss'; import '../formdialog.scss';
import '../../assets/css/clearbutton.scss'; import '../../assets/css/clearbutton.scss';
import '../../assets/css/flexstyles.scss'; import '../../assets/css/flexstyles.scss';
import './style.scss';
import ServerConnections from '../ServerConnections'; import ServerConnections from '../ServerConnections';
import toast from '../toast/toast'; import toast from '../toast/toast';
import { appRouter } from '../appRouter'; import { appRouter } from '../appRouter';

View file

@ -3,7 +3,7 @@
<h3 class="formDialogHeaderTitle"> <h3 class="formDialogHeaderTitle">
${Edit} ${Edit}
</h3> </h3>
<div style="margin-left: auto;" class="flex align-items-center justify-content-center"> <div class="dialogHeader flex align-items-center justify-content-center">
<button is="emby-button" type="button" class="btnHeaderSave button-accent-flat button-flat hide" tabindex="-1"> <button is="emby-button" type="button" class="btnHeaderSave button-accent-flat button-flat hide" tabindex="-1">
<span class="material-icons check" aria-hidden="true"></span> <span class="material-icons check" aria-hidden="true"></span>
<span>${Save}</span> <span>${Save}</span>
@ -29,7 +29,7 @@
<div id="fldPath" class="inputContainer"> <div id="fldPath" class="inputContainer">
<div class="align-items-center flex"> <div class="align-items-center flex">
<div class="flex-grow"> <div class="flex-grow">
<input is="emby-input" id="txtPath" type="text" label="${LabelPath}" class="flex-grow" readonly /> <input is="emby-input" id="txtPath" type="text" label="${LabelPath}" class="flex-grow" readonly dir="ltr"/>
</div> </div>
</div> </div>
</div> </div>
@ -194,7 +194,7 @@
<h2 style="margin:.6em 0;vertical-align:middle;display:inline-block;"> <h2 style="margin:.6em 0;vertical-align:middle;display:inline-block;">
${Genres} ${Genres}
</h2> </h2>
<button is="emby-button" type="button" class="fab btnAddTextItem submit" style="margin-left:1em;" title="${Add}"> <button is="emby-button" type="button" class="fab btnAddTextItem submit marginStart" title="${Add}">
<span class="material-icons add" aria-hidden="true"></span> <span class="material-icons add" aria-hidden="true"></span>
</button> </button>
<div class="paperList" id="listGenres"></div> <div class="paperList" id="listGenres"></div>
@ -203,7 +203,7 @@
<h2 style="margin:.6em 0;vertical-align:middle;display:inline-block;"> <h2 style="margin:.6em 0;vertical-align:middle;display:inline-block;">
${People} ${People}
</h2> </h2>
<button is="emby-button" type="button" id="btnAddPerson" class="fab btnAddPerson" style="margin-left:1em;" title="${Add}"> <button is="emby-button" type="button" id="btnAddPerson" class="fab btnAddPerson marginStart" title="${Add}">
<span class="material-icons add" aria-hidden="true"></span> <span class="material-icons add" aria-hidden="true"></span>
</button> </button>
<div id="peopleList" class="paperList"> <div id="peopleList" class="paperList">
@ -213,7 +213,7 @@
<h2 style="margin:.6em 0;vertical-align:middle;display:inline-block;"> <h2 style="margin:.6em 0;vertical-align:middle;display:inline-block;">
${Studios} ${Studios}
</h2> </h2>
<button is="emby-button" type="button" class="fab btnAddTextItem submit" style="margin-left:1em;" title="${Add}"> <button is="emby-button" type="button" class="fab btnAddTextItem submit marginStart" title="${Add}">
<span class="material-icons add" aria-hidden="true"></span> <span class="material-icons add" aria-hidden="true"></span>
</button> </button>
<div class="paperList" id="listStudios"></div> <div class="paperList" id="listStudios"></div>
@ -222,7 +222,7 @@
<h2 style="margin:.6em 0;vertical-align:middle;display:inline-block;"> <h2 style="margin:.6em 0;vertical-align:middle;display:inline-block;">
${Tags} ${Tags}
</h2> </h2>
<button is="emby-button" type="button" class="fab btnAddTextItem submit" style="margin-left:1em;" title="${Add}"> <button is="emby-button" type="button" class="fab btnAddTextItem submit marginStart" title="${Add}">
<span class="material-icons add" aria-hidden="true"></span> <span class="material-icons add" aria-hidden="true"></span>
</button> </button>
<div class="paperList" id="listTags"></div> <div class="paperList" id="listTags"></div>

View file

@ -0,0 +1,19 @@
.dialogHeader {
[dir="ltr"] & {
margin-left: auto;
}
[dir="rtl"] & {
margin-right: auto;
}
}
.metadataFormFields .marginStart {
[dir="ltr"] & {
margin-left: 1em;
}
[dir="rtl"] & {
margin-right: 1em;
}
}

View file

@ -9,6 +9,7 @@ import alert from '../alert';
import playlistEditor from '../playlisteditor/playlisteditor'; import playlistEditor from '../playlisteditor/playlisteditor';
import confirm from '../confirm/confirm'; import confirm from '../confirm/confirm';
import itemHelper from '../itemHelper'; import itemHelper from '../itemHelper';
import datetime from '../../scripts/datetime';
/* eslint-disable indent */ /* eslint-disable indent */
@ -78,7 +79,7 @@ import itemHelper from '../itemHelper';
if (selectedItems.length) { if (selectedItems.length) {
const itemSelectionCount = document.querySelector('.itemSelectionCount'); const itemSelectionCount = document.querySelector('.itemSelectionCount');
if (itemSelectionCount) { if (itemSelectionCount) {
itemSelectionCount.innerHTML = selectedItems.length; itemSelectionCount.innerHTML = datetime.toLocaleString(selectedItems.length);
} }
} else { } else {
hideSelections(); hideSelections();
@ -129,7 +130,7 @@ import itemHelper from '../itemHelper';
html += '<h1 class="itemSelectionCount"></h1>'; html += '<h1 class="itemSelectionCount"></h1>';
const moreIcon = 'more_vert'; const moreIcon = 'more_vert';
html += `<button is="paper-icon-button-light" class="btnSelectionPanelOptions autoSize" style="margin-left:auto;"><span class="material-icons ${moreIcon}" aria-hidden="true"></span></button>`; html += `<button is="paper-icon-button-light" class="btnSelectionPanelOptions autoSize"><span class="material-icons ${moreIcon}" aria-hidden="true"></span></button>`;
selectionCommandsPanel.innerHTML = html; selectionCommandsPanel.innerHTML = html;

View file

@ -40,3 +40,7 @@
.withMultiSelect { .withMultiSelect {
position: relative; position: relative;
} }
.btnSelectionPanelOptions {
margin-left: auto;
}

View file

@ -48,7 +48,7 @@ import { appRouter } from '../appRouter';
html += '<div class="nowPlayingBar hide nowPlayingBar-hidden">'; html += '<div class="nowPlayingBar hide nowPlayingBar-hidden">';
html += '<div class="nowPlayingBarTop">'; html += '<div class="nowPlayingBarTop">';
html += '<div class="nowPlayingBarPositionContainer sliderContainer">'; html += '<div class="nowPlayingBarPositionContainer sliderContainer" dir="ltr">';
html += '<input type="range" is="emby-slider" pin step=".01" min="0" max="100" value="0" class="slider-medium-thumb nowPlayingBarPositionSlider" data-slider-keep-progress="true"/>'; html += '<input type="range" is="emby-slider" pin step=".01" min="0" max="100" value="0" class="slider-medium-thumb nowPlayingBarPositionSlider" data-slider-keep-progress="true"/>';
html += '</div>'; html += '</div>';
@ -58,7 +58,7 @@ import { appRouter } from '../appRouter';
html += '</div>'; html += '</div>';
// The onclicks are needed due to the return false above // The onclicks are needed due to the return false above
html += '<div class="nowPlayingBarCenter">'; html += '<div class="nowPlayingBarCenter" dir="ltr">';
html += '<button is="paper-icon-button-light" class="previousTrackButton mediaButton"><span class="material-icons skip_previous" aria-hidden="true"></span></button>'; html += '<button is="paper-icon-button-light" class="previousTrackButton mediaButton"><span class="material-icons skip_previous" aria-hidden="true"></span></button>';

View file

@ -54,13 +54,22 @@
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
vertical-align: middle; vertical-align: middle;
text-align: left;
flex-grow: 1; flex-grow: 1;
font-size: 92%; font-size: 92%;
[dir="ltr"] & {
text-align: left;
margin-right: 1em; margin-right: 1em;
margin-left: 0.5em; margin-left: 0.5em;
} }
[dir="rtl"] & {
text-align: right;
margin-left: 1em;
margin-right: 0.5em;
}
}
.nowPlayingBarCenter { .nowPlayingBarCenter {
vertical-align: middle; vertical-align: middle;
text-align: center; text-align: center;
@ -92,7 +101,6 @@
.nowPlayingBarRight { .nowPlayingBarRight {
position: relative; position: relative;
margin: 0 0.5em 0 auto;
/* Need this to make sure it's on top of nowPlayingBarPositionContainer so that buttons are fully clickable */ /* Need this to make sure it's on top of nowPlayingBarPositionContainer so that buttons are fully clickable */
z-index: 2; z-index: 2;
@ -100,6 +108,14 @@
justify-content: flex-end; justify-content: flex-end;
align-items: center; align-items: center;
flex-shrink: 0; flex-shrink: 0;
[dir="ltr"] & {
margin: 0 0.5em 0 auto;
}
[dir="rtl"] & {
margin: 0 auto 0 0.5em;
}
} }
.nowPlayingBarCurrentTime { .nowPlayingBarCurrentTime {
@ -110,9 +126,15 @@
} }
.nowPlayingBarVolumeSliderContainer { .nowPlayingBarVolumeSliderContainer {
[dir="ltr"] & {
margin-right: 2em; margin-right: 2em;
} }
[dir="rtl"] & {
margin-left: 2em;
}
}
.nowPlayingBarUserDataButtons { .nowPlayingBarUserDataButtons {
display: inline-block; display: inline-block;
} }

View file

@ -2,9 +2,16 @@
background: rgba(28, 28, 28, 0.8); background: rgba(28, 28, 28, 0.8);
border-radius: 0.3em; border-radius: 0.3em;
color: #fff; color: #fff;
left: 1.5em;
position: absolute; position: absolute;
top: 5em; top: 5em;
[dir="ltr"] & {
left: 1.5em;
}
[dir="rtl"] & {
right: 1.5em;
}
} }
.playerStats-tv { .playerStats-tv {
@ -31,25 +38,52 @@
.playerStats-stats { .playerStats-stats {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 0 3em 1em 1em;
max-width: 50em; max-width: 50em;
overflow: hidden; overflow: hidden;
[dir="ltr"] & {
padding: 0 3em 1em 1em;
}
[dir="rtl"] & {
padding: 0 1em 1em 3em;
}
} }
.playerStats-stat { .playerStats-stat {
display: flex; display: flex;
[dir="ltr"] & {
margin-left: 1em; margin-left: 1em;
} }
[dir="rtl"] & {
margin-right: 1em;
}
}
.playerStats-stat-label { .playerStats-stat-label {
font-weight: 500; font-weight: 500;
[dir="ltr"] & {
margin: 0 0.5em 0 0; margin: 0 0.5em 0 0;
} }
[dir="rtl"] & {
margin: 0 0 0 0.5em;
}
}
.playerStats-stat-header { .playerStats-stat-header {
[dir="ltr"] & {
margin: 1em 1em 0 0; margin: 1em 1em 0 0;
} }
[dir="rtl"] & {
margin: 1em 0 0 1em;
}
}
.playerStats-stat-value { .playerStats-stat-value {
color: #ddd; color: #ddd;
} }

View file

@ -1,6 +1,13 @@
.recordingButton { .recordingButton {
margin-left: 0;
min-width: 10em; min-width: 10em;
[dir="ltr"] & {
margin-left: 0;
}
[dir="rtl"] & {
margin-right: 0;
}
} }
.recordingIcon-active { .recordingIcon-active {

View file

@ -39,9 +39,15 @@
} }
.nowPlayingPageTitle { .nowPlayingPageTitle {
[dir="ltr"] & {
margin: 0 0 0.5em 0.5em; margin: 0 0 0.5em 0.5em;
} }
[dir="rtl"] & {
margin: 0 0.5em 0.5em 0;
}
}
.nowPlayingAlbum a, .nowPlayingAlbum a,
.nowPlayingArtist a { .nowPlayingArtist a {
font-weight: normal; font-weight: normal;
@ -51,6 +57,10 @@
.nowPlayingButtonsContainer { .nowPlayingButtonsContainer {
display: flex; display: flex;
[dir="rtl"] & {
flex-direction: row-reverse;
}
} }
.infoContainer, .infoContainer,
@ -94,10 +104,17 @@
.nowPlayingPageImageContainer { .nowPlayingPageImageContainer {
width: 16%; width: 16%;
margin-right: 1em;
position: relative; position: relative;
-webkit-flex-shrink: 0; -webkit-flex-shrink: 0;
flex-shrink: 0; flex-shrink: 0;
[dir="ltr"] & {
margin-right: 1em;
}
[dir="rtl"] & {
margin-left: 1em;
}
} }
.nowPlayingPageImageContainerNoAlbum { .nowPlayingPageImageContainerNoAlbum {
@ -268,8 +285,14 @@
@media all and (min-width: 80em) { @media all and (min-width: 80em) {
.nowPlayingPageImageContainer { .nowPlayingPageImageContainer {
[dir="ltr"] & {
margin-right: 0.75em; margin-right: 0.75em;
} }
[dir="rtl"] & {
margin-left: 0.75em;
}
}
} }
@media all and (orientation: portrait) and (max-width: 43em) { @media all and (orientation: portrait) and (max-width: 43em) {
@ -365,24 +388,49 @@
.nowPlayingInfoControls .nowPlayingPageUserDataButtonsTitle button { .nowPlayingInfoControls .nowPlayingPageUserDataButtonsTitle button {
padding-top: 0; padding-top: 0;
border-radius: 0;
[dir="ltr"] & {
padding-right: 0; padding-right: 0;
margin-right: 0; margin-right: 0;
float: right; float: right;
border-radius: 0; }
[dir="rtl"] & {
padding-left: 0;
margin-left: 0;
float: left;
}
} }
.nowPlayingInfoButtons .btnRepeat, .nowPlayingInfoButtons .btnRepeat,
.nowPlayingInfoButtons .btnRewind { .nowPlayingInfoButtons .btnRewind {
font-size: smaller;
[dir="ltr"] & {
margin-left: 0; margin-left: 0;
margin-right: auto; margin-right: auto;
font-size: smaller; }
[dir="rtl"] & {
margin-right: 0;
margin-left: auto;
}
} }
.nowPlayingInfoButtons .btnShuffleQueue, .nowPlayingInfoButtons .btnShuffleQueue,
.nowPlayingInfoButtons .btnFastForward { .nowPlayingInfoButtons .btnFastForward {
font-size: smaller;
[dir="ltr"] & {
margin-left: auto; margin-left: auto;
margin-right: 0; margin-right: 0;
font-size: smaller; }
[dir="rtl"] & {
margin-right: auto;
margin-left: 0;
}
} }
.paper-icon-button-light { .paper-icon-button-light {
@ -413,7 +461,7 @@
.nowPlayingButtonsContainer { .nowPlayingButtonsContainer {
display: flex; display: flex;
flex-direction: column; flex-direction: column !important;
} }
} }

View file

@ -9,3 +9,7 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.raised.raised-mini.btnBrowse {
margin-left: 1.5em;
}

View file

@ -13,7 +13,7 @@
<div class="flex align-items-center" style="margin:1.5em 0;"> <div class="flex align-items-center" style="margin:1.5em 0;">
<h2 style="margin:0;">${HeaderAddUpdateSubtitle}</h2> <h2 style="margin:0;">${HeaderAddUpdateSubtitle}</h2>
<button is="emby-button" type="button" class="raised raised-mini btnBrowse" style="margin-left:1.5em;"> <button is="emby-button" type="button" class="raised raised-mini btnBrowse">
<span class="material-icons folder" aria-hidden="true"></span> <span class="material-icons folder" aria-hidden="true"></span>
<span>${Browse}</span> <span>${Browse}</span>
</button> </button>

View file

@ -1,6 +1,5 @@
.toastContainer { .toastContainer {
position: fixed; position: fixed;
left: 0;
bottom: 0; bottom: 0;
pointer-events: none; pointer-events: none;
z-index: 9999999; z-index: 9999999;
@ -12,6 +11,14 @@
padding-bottom: max(env(safe-area-inset-bottom), 1em); padding-bottom: max(env(safe-area-inset-bottom), 1em);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
[dir="ltr"] & {
left: 0;
}
[dir="rtl"] & {
right: 0;
}
} }
.toast { .toast {

View file

@ -8,6 +8,7 @@ import '../../elements/emby-button/paper-icon-button-light';
import '../../elements/emby-select/emby-select'; import '../../elements/emby-select/emby-select';
import '../../elements/emby-button/emby-button'; import '../../elements/emby-button/emby-button';
import '../../assets/css/flexstyles.scss'; import '../../assets/css/flexstyles.scss';
import './style.scss';
import Dashboard from '../../utils/dashboard'; import Dashboard from '../../utils/dashboard';
import Events from '../../utils/events.ts'; import Events from '../../utils/events.ts';

View file

@ -37,7 +37,7 @@
<h2 style="background-color:rgba(82,181,75,.8);color:#fff;margin: 0;border-radius:100em;height:1.7em;width:1.7em;" class="flex align-items-center justify-content-center"> <h2 style="background-color:rgba(82,181,75,.8);color:#fff;margin: 0;border-radius:100em;height:1.7em;width:1.7em;" class="flex align-items-center justify-content-center">
2 2
</h2> </h2>
<h3 style="margin:0 0 0 .5em;"> <h3 class="guideProviderSelectListings">
${GuideProviderSelectListings} ${GuideProviderSelectListings}
</h3> </h3>
</div> </div>

View file

@ -0,0 +1,9 @@
.guideProviderSelectListings {
[dir="ltr"] & {
margin: 0 0 0 0.5em;
}
[dir="rtl"] & {
margin: 0 0.5em 0 0;
}
}

View file

@ -4,7 +4,6 @@
bottom: 0; bottom: 0;
width: 30em; width: 30em;
padding: 1em; padding: 1em;
margin: 0 2em 2em 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
will-change: transform, opacity; will-change: transform, opacity;
@ -13,6 +12,14 @@
color: #fff; color: #fff;
user-select: none; user-select: none;
-webkit-touch-callout: none; -webkit-touch-callout: none;
[dir="ltr"] & {
margin: 0 2em 2em 0;
}
[dir="rtl"] & {
margin: 0 0 2em 2em;
}
} }
.upNextDialog-hidden { .upNextDialog-hidden {

View file

@ -80,31 +80,31 @@
<div class="listItem listItem-border"> <div class="listItem listItem-border">
<div class="listItemBody two-line"> <div class="listItemBody two-line">
<div class="listItemBodyText secondary" style="margin:0;">${LabelCache}</div> <div class="listItemBodyText secondary" style="margin:0;">${LabelCache}</div>
<div class="listItemBodyText" id="cachePath"></div> <div class="listItemBodyText" id="cachePath" dir="ltr" style="text-align: left;"></div>
</div> </div>
</div> </div>
<div class="listItem listItem-border"> <div class="listItem listItem-border">
<div class="listItemBody two-line"> <div class="listItemBody two-line">
<div class="listItemBodyText secondary" style="margin:0;">${LabelLogs}</div> <div class="listItemBodyText secondary" style="margin:0;">${LabelLogs}</div>
<div class="listItemBodyText" id="logPath"></div> <div class="listItemBodyText" id="logPath" dir="ltr" style="text-align: left;"></div>
</div> </div>
</div> </div>
<div class="listItem listItem-border"> <div class="listItem listItem-border">
<div class="listItemBody two-line"> <div class="listItemBody two-line">
<div class="listItemBodyText secondary" style="margin:0;">${LabelMetadata}</div> <div class="listItemBodyText secondary" style="margin:0;">${LabelMetadata}</div>
<div class="listItemBodyText" id="metadataPath"></div> <div class="listItemBodyText" id="metadataPath" dir="ltr" style="text-align: left;"></div>
</div> </div>
</div> </div>
<div class="listItem listItem-border"> <div class="listItem listItem-border">
<div class="listItemBody two-line"> <div class="listItemBody two-line">
<div class="listItemBodyText secondary" style="margin:0;">${LabelTranscodes}</div> <div class="listItemBodyText secondary" style="margin:0;">${LabelTranscodes}</div>
<div class="listItemBodyText" id="transcodePath"></div> <div class="listItemBodyText" id="transcodePath" dir="ltr" style="text-align: left;"></div>
</div> </div>
</div> </div>
<div class="listItem listItem-border"> <div class="listItem listItem-border">
<div class="listItemBody two-line"> <div class="listItemBody two-line">
<div class="listItemBodyText secondary" style="margin:0;">${LabelWeb}</div> <div class="listItemBodyText secondary" style="margin:0;">${LabelWeb}</div>
<div class="listItemBodyText" id="webPath"></div> <div class="listItemBodyText" id="webPath" dir="ltr" style="text-align: left;"></div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -280,7 +280,7 @@ import confirm from '../../components/confirm/confirm';
html += clientImage; html += clientImage;
} }
html += '<div class="sessionAppName" style="display:inline-block;">'; html += '<div class="sessionAppName" style="display:inline-block; text-align:left;" dir="ltr" >';
html += '<div class="sessionDeviceName">' + escapeHtml(session.DeviceName) + '</div>'; html += '<div class="sessionDeviceName">' + escapeHtml(session.DeviceName) + '</div>';
html += '<div class="sessionAppSecondaryText">' + escapeHtml(DashboardPage.getAppSecondaryText(session)) + '</div>'; html += '<div class="sessionAppSecondaryText">' + escapeHtml(DashboardPage.getAppSecondaryText(session)) + '</div>';
html += '</div>'; html += '</div>';

View file

@ -115,6 +115,9 @@ import confirm from '../../../components/confirm/confirm';
deviceHtml += '<div class="cardFooter">'; deviceHtml += '<div class="cardFooter">';
if (canEdit || canDelete(device.Id)) { if (canEdit || canDelete(device.Id)) {
if (globalize.getIsRTL())
deviceHtml += '<div style="text-align:left; float:left;padding-top:5px;">';
else
deviceHtml += '<div style="text-align:right; float:right;padding-top:5px;">'; deviceHtml += '<div style="text-align:right; float:right;padding-top:5px;">';
deviceHtml += '<button type="button" is="paper-icon-button-light" data-id="' + device.Id + '" title="' + globalize.translate('Menu') + '" class="btnDeviceMenu"><span class="material-icons more_vert" aria-hidden="true"></span></button>'; deviceHtml += '<button type="button" is="paper-icon-button-light" data-id="' + device.Id + '" title="' + globalize.translate('Menu') + '" class="btnDeviceMenu"><span class="material-icons more_vert" aria-hidden="true"></span></button>';
deviceHtml += '</div>'; deviceHtml += '</div>';

View file

@ -220,7 +220,7 @@
<div class="inputContainer fldEncoderPath"> <div class="inputContainer fldEncoderPath">
<div style="display: flex; align-items: center;"> <div style="display: flex; align-items: center;">
<div style="flex-grow:1;"> <div style="flex-grow:1;">
<input is="emby-input" class="txtEncoderPath" label="${LabelffmpegPath}" autocomplete="off" /> <input is="emby-input" class="txtEncoderPath" label="${LabelffmpegPath}" autocomplete="off" dir="ltr" />
</div> </div>
<button type="button" is="paper-icon-button-light" id="btnSelectEncoderPath" class="emby-input-iconbutton"><span class="material-icons search" aria-hidden="true"></span></button> <button type="button" is="paper-icon-button-light" id="btnSelectEncoderPath" class="emby-input-iconbutton"><span class="material-icons search" aria-hidden="true"></span></button>
</div> </div>
@ -231,7 +231,7 @@
<div class="inputContainer"> <div class="inputContainer">
<div style="display: flex; align-items: center;"> <div style="display: flex; align-items: center;">
<div style="flex-grow:1;"> <div style="flex-grow:1;">
<input is="emby-input" id="txtTranscodingTempPath" label="${LabelTranscodePath}" autocomplete="off" /> <input is="emby-input" id="txtTranscodingTempPath" label="${LabelTranscodePath}" autocomplete="off" dir="ltr" />
</div> </div>
<button type="button" is="paper-icon-button-light" id="btnSelectTranscodingTempPath" class="emby-input-iconbutton"><span class="material-icons search" aria-hidden="true"></span></button> <button type="button" is="paper-icon-button-light" id="btnSelectTranscodingTempPath" class="emby-input-iconbutton"><span class="material-icons search" aria-hidden="true"></span></button>
</div> </div>
@ -240,7 +240,7 @@
<div class="inputContainer"> <div class="inputContainer">
<div style="display: flex; align-items: center;"> <div style="display: flex; align-items: center;">
<div style="flex-grow:1;"> <div style="flex-grow:1;">
<input is="emby-input" id="txtFallbackFontPath" label="${LabelFallbackFontPath}" autocomplete="off" /> <input is="emby-input" id="txtFallbackFontPath" label="${LabelFallbackFontPath}" autocomplete="off" dir="ltr" />
</div> </div>
<button type="button" is="paper-icon-button-light" id="btnSelectFallbackFontPath" class="emby-input-iconbutton"><span class="material-icons search" aria-hidden="true"></span></button> <button type="button" is="paper-icon-button-light" id="btnSelectFallbackFontPath" class="emby-input-iconbutton"><span class="material-icons search" aria-hidden="true"></span></button>
</div> </div>

View file

@ -30,7 +30,7 @@
<div class="inputContainer"> <div class="inputContainer">
<div style="display: flex; align-items: center;"> <div style="display: flex; align-items: center;">
<div style="flex-grow:1;"> <div style="flex-grow:1;">
<input is="emby-input" id="txtCachePath" label="${LabelCachePath}" autocomplete="off" /> <input is="emby-input" id="txtCachePath" label="${LabelCachePath}" autocomplete="off" dir="ltr" />
</div> </div>
<button type="button" is="paper-icon-button-light" id="btnSelectCachePath" title="${ButtonSelectDirectory}" class="emby-input-iconbutton"><span class="material-icons search" aria-hidden="true"></span></button> <button type="button" is="paper-icon-button-light" id="btnSelectCachePath" title="${ButtonSelectDirectory}" class="emby-input-iconbutton"><span class="material-icons search" aria-hidden="true"></span></button>
</div> </div>
@ -40,7 +40,7 @@
<div class="inputContainer"> <div class="inputContainer">
<div style="display: flex; align-items: center;"> <div style="display: flex; align-items: center;">
<div style="flex-grow:1;"> <div style="flex-grow:1;">
<input is="emby-input" id="txtMetadataPath" label="${LabelMetadataPath}" autocomplete="off" /> <input is="emby-input" id="txtMetadataPath" label="${LabelMetadataPath}" autocomplete="off" dir="ltr" />
</div> </div>
<button type="button" is="paper-icon-button-light" id="btnSelectMetadataPath" title="${ButtonSelectDirectory}" class="emby-input-iconbutton"><span class="material-icons search" aria-hidden="true"></span></button> <button type="button" is="paper-icon-button-light" id="btnSelectMetadataPath" title="${ButtonSelectDirectory}" class="emby-input-iconbutton"><span class="material-icons search" aria-hidden="true"></span></button>
</div> </div>

View file

@ -310,7 +310,10 @@ import cardBuilder from '../../components/cardbuilder/cardBuilder';
html += '<div class="cardFooter visualCardBox-cardFooter">'; // always show menu unless explicitly hidden html += '<div class="cardFooter visualCardBox-cardFooter">'; // always show menu unless explicitly hidden
if (virtualFolder.showMenu !== false) { if (virtualFolder.showMenu !== false) {
html += '<div style="text-align:right; float:right;padding-top:5px;">'; let dirTextAlign = 'right';
if (globalize.getIsRTL())
dirTextAlign = 'left';
html += '<div style="text-align:' + dirTextAlign + '; float:' + dirTextAlign + ';padding-top:5px;">';
html += '<button type="button" is="paper-icon-button-light" class="btnCardMenu autoSize"><span class="material-icons more_vert" aria-hidden="true"></span></button>'; html += '<button type="button" is="paper-icon-button-light" class="btnCardMenu autoSize"><span class="material-icons more_vert" aria-hidden="true"></span></button>';
html += '</div>'; html += '</div>';
} }
@ -343,7 +346,7 @@ import cardBuilder from '../../components/cardbuilder/cardBuilder';
html += '&nbsp;'; html += '&nbsp;';
html += '</div>'; html += '</div>';
} else if (virtualFolder.Locations.length && virtualFolder.Locations.length === 1) { } else if (virtualFolder.Locations.length && virtualFolder.Locations.length === 1) {
html += "<div class='cardText cardText-secondary'>"; html += "<div class='cardText cardText-secondary' dir='ltr' style='text-align:left;'>";
html += virtualFolder.Locations[0]; html += virtualFolder.Locations[0];
html += '</div>'; html += '</div>';
} else { } else {

View file

@ -42,7 +42,7 @@ import alert from '../../components/alert';
let logHtml = ''; let logHtml = '';
logHtml += '<a is="emby-linkbutton" href="' + logUrl + '" target="_blank" class="listItem listItem-border" style="color:inherit;">'; logHtml += '<a is="emby-linkbutton" href="' + logUrl + '" target="_blank" class="listItem listItem-border" style="color:inherit;">';
logHtml += '<div class="listItemBody two-line">'; logHtml += '<div class="listItemBody two-line">';
logHtml += "<h3 class='listItemBodyText'>" + log.Name + '</h3>'; logHtml += "<h3 class='listItemBodyText' dir='ltr' style='text-align: left'>" + log.Name + '</h3>';
const date = datetime.parseISO8601Date(log.DateModified, true); const date = datetime.parseISO8601Date(log.DateModified, true);
let text = datetime.toLocaleDateString(date); let text = datetime.toLocaleDateString(date);
text += ' ' + datetime.getDisplayTime(date); text += ' ' + datetime.getDisplayTime(date);

View file

@ -83,6 +83,9 @@ function getPluginCardHtml(plugin, pluginConfigurationPages) {
html += '<div class="cardFooter">'; html += '<div class="cardFooter">';
if (configPage || plugin.CanUninstall) { if (configPage || plugin.CanUninstall) {
if (globalize.getIsRTL())
html += '<div style="text-align:left; float:left;padding-top:5px;">';
else
html += '<div style="text-align:right; float:right;padding-top:5px;">'; html += '<div style="text-align:right; float:right;padding-top:5px;">';
html += '<button type="button" is="paper-icon-button-light" class="btnCardMenu autoSize"><span class="material-icons more_vert" aria-hidden="true"></span></button>'; html += '<button type="button" is="paper-icon-button-light" class="btnCardMenu autoSize"><span class="material-icons more_vert" aria-hidden="true"></span></button>';
html += '</div>'; html += '</div>';

View file

@ -59,7 +59,10 @@ import '../../../elements/emby-button/emby-button';
html += '<span class="material-icons listItemIcon schedule" aria-hidden="true"></span>'; html += '<span class="material-icons listItemIcon schedule" aria-hidden="true"></span>';
html += '</a>'; html += '</a>';
html += '<div class="listItemBody two-line">'; html += '<div class="listItemBody two-line">';
html += "<a class='clearLink' style='margin:0;padding:0;display:block;text-align:left;' is='emby-linkbutton' href='scheduledtask.html?id=" + task.Id + "'>"; let textAlignStyle = 'left';
if (globalize.getIsRTL())
textAlignStyle = 'right';
html += "<a class='clearLink' style='margin:0;padding:0;display:block;text-align:" + textAlignStyle + ";' is='emby-linkbutton' href='scheduledtask.html?id=" + task.Id + "'>";
html += "<h3 class='listItemBodyText'>" + task.Name + '</h3>'; html += "<h3 class='listItemBodyText'>" + task.Name + '</h3>';
html += "<div class='secondary listItemBodyText' id='taskProgress" + task.Id + "'>" + getTaskProgressHtml(task) + '</div>'; html += "<div class='secondary listItemBodyText' id='taskProgress" + task.Id + "'>" + getTaskProgressHtml(task) + '</div>';
html += '</a>'; html += '</a>';

View file

@ -476,7 +476,7 @@ function renderName(item, container, context) {
html = '<h3 class="parentName musicParentName focuscontainer-x">' + parentNameHtml.join(' - ') + '</h3>'; html = '<h3 class="parentName musicParentName focuscontainer-x">' + parentNameHtml.join(' - ') + '</h3>';
} }
} else { } else {
html = '<h1 class="parentName focuscontainer-x">' + tvShowHtml + '</h1>'; html = '<h1 class="parentName focuscontainer-x"><bdi>' + tvShowHtml + '</bdi></h1>';
} }
} }
@ -486,14 +486,14 @@ function renderName(item, container, context) {
if (html && !parentNameLast) { if (html && !parentNameLast) {
if (tvSeasonHtml) { if (tvSeasonHtml) {
html += '<h3 class="itemName infoText subtitle focuscontainer-x">' + tvSeasonHtml + ' - ' + name + '</h3>'; html += '<h3 class="itemName infoText subtitle focuscontainer-x"><bdi>' + tvSeasonHtml + ' - ' + name + '</bdi></h3>';
} else { } else {
html += '<h3 class="itemName infoText subtitle">' + name + '</h3>'; html += '<h3 class="itemName infoText subtitle"><bdi>' + name + '</bdi></h3>';
} }
} else if (item.OriginalTitle && item.OriginalTitle != item.Name) { } else if (item.OriginalTitle && item.OriginalTitle != item.Name) {
html = '<h1 class="itemName infoText parentNameLast withOriginalTitle">' + name + '</h1>' + html; html = '<h1 class="itemName infoText parentNameLast withOriginalTitle"><bdi>' + name + '</bdi></h1>' + html;
} else { } else {
html = '<h1 class="itemName infoText parentNameLast">' + name + '</h1>' + html; html = '<h1 class="itemName infoText parentNameLast"><bdi>' + name + '</bdi></h1>' + html;
} }
if (item.OriginalTitle && item.OriginalTitle != item.Name) { if (item.OriginalTitle && item.OriginalTitle != item.Name) {
@ -915,7 +915,7 @@ function renderOverview(page, item) {
if (overview) { if (overview) {
for (const overviewElemnt of overviewElements) { for (const overviewElemnt of overviewElements) {
overviewElemnt.innerHTML = overview; overviewElemnt.innerHTML = '<bdi>' + overview + '</bdi>';
overviewElemnt.classList.remove('hide'); overviewElemnt.classList.remove('hide');
overviewElemnt.classList.add('detail-clamp-text'); overviewElemnt.classList.add('detail-clamp-text');
@ -1068,7 +1068,7 @@ function renderTagline(page, item) {
if (item.Taglines && item.Taglines.length) { if (item.Taglines && item.Taglines.length) {
taglineElement.classList.remove('hide'); taglineElement.classList.remove('hide');
taglineElement.innerText = item.Taglines[0]; taglineElement.innerHTML = '<bdi>' + item.Taglines[0] + '</bdi>';
} else { } else {
taglineElement.classList.add('hide'); taglineElement.classList.add('hide');
} }

View file

@ -18,7 +18,7 @@
</div> </div>
<div class="sliderContainer flex"> <div class="sliderContainer flex" dir="ltr">
<div class="positionTime"></div> <div class="positionTime"></div>
<div class="nowPlayingPositionSliderContainer"> <div class="nowPlayingPositionSliderContainer">
<input type="range" is="emby-slider" pin step="1" min="0" max="100" value="0" class="nowPlayingPositionSlider" data-slider-keep-progress="true" /> <input type="range" is="emby-slider" pin step="1" min="0" max="100" value="0" class="nowPlayingPositionSlider" data-slider-keep-progress="true" />
@ -28,7 +28,7 @@
<div class="nowPlayingButtonsContainer focuscontainer-x justify-content-space-between"> <div class="nowPlayingButtonsContainer focuscontainer-x justify-content-space-between">
<div class="nowPlayingInfoButtons"> <div class="nowPlayingInfoButtons" dir="ltr">
<button is="paper-icon-button-light" class="btnCommand btnRepeat repeatToggleButton autoSize" title="${Repeat}" <button is="paper-icon-button-light" class="btnCommand btnRepeat repeatToggleButton autoSize" title="${Repeat}"
data-command="SetRepeatMode"> data-command="SetRepeatMode">

View file

@ -18,7 +18,7 @@
<div class="osdTextContainer osdSecondaryMediaInfo"></div> <div class="osdTextContainer osdSecondaryMediaInfo"></div>
<div class="flex flex-direction-row align-items-center"> <div class="flex flex-direction-row align-items-center" dir="ltr">
<div class="osdTextContainer startTimeText osdPositionText" style="margin: 0 .25em 0 0;"></div> <div class="osdTextContainer startTimeText osdPositionText" style="margin: 0 .25em 0 0;"></div>
<div class="sliderContainer flex-grow" style="margin: .5em 0 .25em;"> <div class="sliderContainer flex-grow" style="margin: .5em 0 .25em;">
<div class="sliderMarkerContainer"></div> <div class="sliderMarkerContainer"></div>
@ -28,6 +28,7 @@
</div> </div>
<div class="buttons focuscontainer-x"> <div class="buttons focuscontainer-x">
<div dir="ltr">
<button is="paper-icon-button-light" class="btnRecord autoSize hide"> <button is="paper-icon-button-light" class="btnRecord autoSize hide">
<span class="xlargePaperIconButton material-icons fiber_manual_record" aria-hidden="true"></span> <span class="xlargePaperIconButton material-icons fiber_manual_record" aria-hidden="true"></span>
</button> </button>
@ -59,6 +60,7 @@
<button is="paper-icon-button-light" class="btnNextTrack autoSize hide" title="${NextTrack}"> <button is="paper-icon-button-light" class="btnNextTrack autoSize hide" title="${NextTrack}">
<span class="xlargePaperIconButton material-icons skip_next" aria-hidden="true"></span> <span class="xlargePaperIconButton material-icons skip_next" aria-hidden="true"></span>
</button> </button>
</div>
<div class="osdTimeText"> <div class="osdTimeText">
<span class="endsAtText"></span> <span class="endsAtText"></span>

View file

@ -216,7 +216,7 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components
let title = itemName; let title = itemName;
if (item.PremiereDate) { if (item.PremiereDate) {
try { try {
const year = datetime.parseISO8601Date(item.PremiereDate).getFullYear(); const year = datetime.toLocaleString(datetime.parseISO8601Date(item.PremiereDate).getFullYear(), {useGrouping: false});
title += ` (${year})`; title += ` (${year})`;
} catch (e) { } catch (e) {
console.error(e); console.error(e);

View file

@ -17,20 +17,36 @@
align-items: center; align-items: center;
text-transform: none; text-transform: none;
width: 100%; width: 100%;
text-align: left;
border-width: 0 0 0.1em 0; border-width: 0 0 0.1em 0;
border-style: solid; border-style: solid;
padding-left: 0.1em; padding-left: 0.1em;
background: transparent; background: transparent;
box-shadow: none; box-shadow: none;
[dir="ltr"] & {
text-align: left;
padding-left: 0.1em;
}
[dir="rtl"] & {
text-align: right;
padding-right: 0.1em;
}
} }
.emby-collapse-expandIcon { .emby-collapse-expandIcon {
transform-origin: 50% 50%; transform-origin: 50% 50%;
transition: transform 180ms ease-out; transition: transform 180ms ease-out;
position: absolute; position: absolute;
right: 0.5em;
font-size: 1.5em; font-size: 1.5em;
[dir="ltr"] & {
right: 0.5em;
}
[dir="rtl"] & {
left: 0.5em;
}
} }
.emby-collapse-expandIconExpanded { .emby-collapse-expandIconExpanded {

View file

@ -45,13 +45,17 @@
margin: 0 0.5em 1.8em; margin: 0 0.5em 1.8em;
} }
.inlineForm .inputContainer:first-child, [dir="rtl"] .inlineForm .inputContainer:last-child,
.inlineForm .selectContainer:first-child { [dir="rtl"] .inlineForm .selectContainer:last-child,
[dir="ltr"] .inlineForm .inputContainer:first-child,
[dir="ltr"] .inlineForm .selectContainer:first-child {
margin-left: 0; margin-left: 0;
} }
.inlineForm .inputContainer:last-child, [dir="ltr"] .inlineForm .inputContainer:last-child,
.inlineForm .selectContainer:last-child { [dir="ltr"] .inlineForm .selectContainer:last-child,
[dir="rtl"] .inlineForm .inputContainer:first-child,
[dir="rtl"] .inlineForm .selectContainer:first-child {
margin-right: 0; margin-right: 0;
} }

View file

@ -1,6 +1,7 @@
import './emby-progressring.scss'; import './emby-progressring.scss';
import 'webcomponents.js/webcomponents-lite'; import 'webcomponents.js/webcomponents-lite';
import template from './emby-progressring.template.html'; import template from './emby-progressring.template.html';
import { getCurrentDateTimeLocale } from '../../scripts/globalize';
/* eslint-disable indent */ /* eslint-disable indent */
@ -8,6 +9,7 @@ import template from './emby-progressring.template.html';
EmbyProgressRing.createdCallback = function () { EmbyProgressRing.createdCallback = function () {
this.classList.add('progressring'); this.classList.add('progressring');
this.setAttribute('dir', 'ltr');
const instance = this; const instance = this;
instance.innerHTML = template; instance.innerHTML = template;
@ -70,7 +72,10 @@ import template from './emby-progressring.template.html';
this.querySelector('.animate-75-100-b').style.transform = 'rotate(' + angle + 'deg)'; this.querySelector('.animate-75-100-b').style.transform = 'rotate(' + angle + 'deg)';
} }
this.querySelector('.progressring-text').innerHTML = progress + '%'; this.querySelector('.progressring-text').innerHTML = new Intl.NumberFormat(getCurrentDateTimeLocale(), {
style: 'percent',
maximumFractionDigits: 0
}).format(progress / 100);
}; };
EmbyProgressRing.attachedCallback = function () { EmbyProgressRing.attachedCallback = function () {

View file

@ -32,11 +32,18 @@
.mdl-radio__circles { .mdl-radio__circles {
position: relative; position: relative;
margin-right: 0.54em;
width: 1.08em; width: 1.08em;
height: 1.08em; height: 1.08em;
border-radius: 50%; border-radius: 50%;
cursor: pointer; cursor: pointer;
[dir="ltr"] & {
margin-right: 0.54em;
}
[dir="rtl"] & {
margin-left: 0.54em;
}
} }
.mdl-radio__circles svg { .mdl-radio__circles svg {
@ -44,9 +51,16 @@
height: 100%; height: 100%;
position: absolute; position: absolute;
top: 0; top: 0;
left: 0;
z-index: 1; z-index: 1;
overflow: visible; overflow: visible;
[dir="ltr"] & {
left: 0;
}
[dir="rtl"] & {
right: 0;
}
} }
.mdl-radio__button:disabled + .mdl-radio__circles { .mdl-radio__button:disabled + .mdl-radio__circles {

View file

@ -40,6 +40,11 @@ const EmbyScrollButtonsPrototype = Object.create(HTMLDivElement.prototype);
} }
function updateScrollButtons(scrollButtons, scrollSize, scrollPos, scrollWidth) { function updateScrollButtons(scrollButtons, scrollSize, scrollPos, scrollWidth) {
let localeAwarePos = scrollPos;
if (globalize.getIsElementRTL(scrollButtons)) {
localeAwarePos *= -1;
}
// TODO: Check if hack is really needed // TODO: Check if hack is really needed
// hack alert add twenty for rounding errors // hack alert add twenty for rounding errors
if (scrollWidth <= scrollSize + 20) { if (scrollWidth <= scrollSize + 20) {
@ -50,13 +55,13 @@ const EmbyScrollButtonsPrototype = Object.create(HTMLDivElement.prototype);
scrollButtons.scrollButtonsRight.classList.remove('hide'); scrollButtons.scrollButtonsRight.classList.remove('hide');
} }
if (scrollPos > 0) { if (localeAwarePos > 0) {
scrollButtons.scrollButtonsLeft.disabled = false; scrollButtons.scrollButtonsLeft.disabled = false;
} else { } else {
scrollButtons.scrollButtonsLeft.disabled = true; scrollButtons.scrollButtonsLeft.disabled = true;
} }
const scrollPosEnd = scrollPos + scrollSize; const scrollPosEnd = localeAwarePos + scrollSize;
if (scrollWidth > 0 && scrollPosEnd >= scrollWidth) { if (scrollWidth > 0 && scrollPosEnd >= scrollWidth) {
scrollButtons.scrollButtonsRight.disabled = true; scrollButtons.scrollButtonsRight.disabled = true;
} else { } else {
@ -138,6 +143,12 @@ const EmbyScrollButtonsPrototype = Object.create(HTMLDivElement.prototype);
newPos = scrollPos + scrollSize; newPos = scrollPos + scrollSize;
} }
if (globalize.getIsRTL() && direction === 'left') {
newPos = scrollPos + scrollSize;
} else if (globalize.getIsRTL()) {
newPos = Math.min(0, scrollPos - scrollSize);
}
scroller.scrollToPosition(newPos, false); scroller.scrollToPosition(newPos, false);
} }

View file

@ -1,7 +1,6 @@
.emby-scrollbuttons { .emby-scrollbuttons {
position: absolute; position: absolute;
top: 0; top: 0;
right: 0;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
min-width: 104px; min-width: 104px;
@ -10,6 +9,14 @@
z-index: 1; z-index: 1;
color: #fff; color: #fff;
display: flex; display: flex;
[dir="ltr"] & {
right: 0;
}
[dir="rtl"] & {
left: 0;
}
} }
.emby-scrollbuttons-button > .material-icons { .emby-scrollbuttons-button > .material-icons {

View file

@ -9,15 +9,22 @@
margin-right: max(env(safe-area-inset-right), 3.3%); margin-right: max(env(safe-area-inset-right), 3.3%);
} }
.servers > .card > .cardBox {
margin-left: 0.6em;
margin-right: 0.6em;
}
/* align first card in scroller to heading */ /* align first card in scroller to heading */
.itemsContainer > .card > .cardBox { .itemsContainer > .card > .cardBox {
[dir="ltr"] & {
margin-left: 0; margin-left: 0;
margin-right: 1.2em; margin-right: 1.2em;
} }
.servers > .card > .cardBox { [dir="rtl"] & {
margin-left: 0.6em; margin-right: 0;
margin-right: 0.6em; margin-left: 1.2em;
}
} }
.layout-tv .emby-scroller, .layout-tv .emby-scroller,

View file

@ -11,13 +11,20 @@
/* General select styles: change as needed */ /* General select styles: change as needed */
font-family: inherit; font-family: inherit;
font-weight: inherit; font-weight: inherit;
padding: 0.5em 1.9em 0.5em 0.5em;
/* Prevent padding from causing width overflow */ /* Prevent padding from causing width overflow */
box-sizing: border-box; box-sizing: border-box;
outline: none !important; outline: none !important;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0); -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
width: 100%; width: 100%;
[dir="ltr"] & {
padding: 0.5em 1.9em 0.5em 0.5em;
}
[dir="rtl"] & {
padding: 0.5em 0.5em 0.5em 1.9em;
}
} }
.emby-select[disabled] { .emby-select[disabled] {
@ -34,8 +41,15 @@
} }
.selectContainer-inline > .emby-select { .selectContainer-inline > .emby-select {
padding: 0.3em 1.9em 0.3em 0.5em;
font-size: inherit; font-size: inherit;
[dir="ltr"] & {
padding: 0.3em 1.9em 0.3em 0.5em;
}
[dir="rtl"] & {
padding: 0.3em 0.5em 0.3em 1.9em;
}
} }
.selectContainer-inline > .emby-select[disabled] { .selectContainer-inline > .emby-select[disabled] {
@ -92,10 +106,17 @@
.selectArrowContainer { .selectArrowContainer {
position: absolute; position: absolute;
right: 0.3em;
top: 0.2em; top: 0.2em;
color: inherit; color: inherit;
pointer-events: none; pointer-events: none;
[dir="ltr"] & {
right: 0.3em;
}
[dir="rtl"] & {
left: 0.3em;
}
} }
.selectContainer-inline > .selectArrowContainer { .selectContainer-inline > .selectArrowContainer {

View file

@ -5,6 +5,7 @@ import keyboardnavigation from '../../scripts/keyboardNavigation';
import './emby-slider.scss'; import './emby-slider.scss';
import 'webcomponents.js/webcomponents-lite'; import 'webcomponents.js/webcomponents-lite';
import '../emby-input/emby-input'; import '../emby-input/emby-input';
import globalize from '../../scripts/globalize';
/* eslint-disable indent */ /* eslint-disable indent */
@ -31,6 +32,8 @@ import '../emby-input/emby-input';
const rect = range.sliderBubbleTrack.getBoundingClientRect(); const rect = range.sliderBubbleTrack.getBoundingClientRect();
let fraction = (clientX - rect.left) / rect.width; let fraction = (clientX - rect.left) / rect.width;
if (globalize.getIsElementRTL(range))
fraction = (rect.right - clientX) / rect.width;
// Snap to step // Snap to step
const valueRange = range.max - range.min; const valueRange = range.max - range.min;
@ -118,6 +121,9 @@ import '../emby-input/emby-input';
const bubbleRect = bubble.getBoundingClientRect(); const bubbleRect = bubble.getBoundingClientRect();
let bubblePos = bubbleTrackRect.width * value / 100; let bubblePos = bubbleTrackRect.width * value / 100;
if (globalize.getIsElementRTL(range)) {
bubblePos = bubbleTrackRect.width - bubblePos;
}
bubblePos = Math.min(Math.max(bubblePos, bubbleRect.width / 2), bubbleTrackRect.width - bubbleRect.width / 2); bubblePos = Math.min(Math.max(bubblePos, bubbleRect.width / 2), bubbleTrackRect.width - bubbleRect.width / 2);
bubble.style.left = bubblePos + 'px'; bubble.style.left = bubblePos + 'px';
@ -484,6 +490,9 @@ import '../emby-input/emby-input';
function setRange(elem, startPercent, endPercent) { function setRange(elem, startPercent, endPercent) {
const style = elem.style; const style = elem.style;
if (globalize.getIsRTL())
style.right = Math.max(startPercent, 0) + '%';
else
style.left = Math.max(startPercent, 0) + '%'; style.left = Math.max(startPercent, 0) + '%';
const widthPercent = endPercent - startPercent; const widthPercent = endPercent - startPercent;

View file

@ -151,9 +151,16 @@
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
top: 50%; top: 50%;
left: 0;
position: absolute; position: absolute;
padding: 0 0.54em; /* half of slider thumb size */ padding: 0 0.54em; /* half of slider thumb size */
[dir="ltr"] & {
left: 0;
}
[dir="rtl"] & {
right: 0;
}
} }
.mdl-slider-background-flex { .mdl-slider-background-flex {
@ -162,11 +169,18 @@
margin-top: -0.1em; margin-top: -0.1em;
width: 100%; width: 100%;
top: 50%; top: 50%;
left: 0;
display: flex; display: flex;
overflow: hidden; overflow: hidden;
border: 0; border: 0;
padding: 0; padding: 0;
[dir="ltr"] & {
left: 0;
}
[dir="rtl"] & {
right: 0;
}
} }
.mdl-slider-background-flex-inner { .mdl-slider-background-flex-inner {
@ -177,11 +191,18 @@
.mdl-slider-background-lower { .mdl-slider-background-lower {
/* transition: width 0.18s cubic-bezier(0.4, 0, 0.2, 1); */ /* transition: width 0.18s cubic-bezier(0.4, 0, 0.2, 1); */
position: absolute; position: absolute;
left: 0;
width: 0; width: 0;
top: 0; top: 0;
bottom: 0; bottom: 0;
background-color: #00a4dc; background-color: #00a4dc;
[dir="ltr"] & {
left: 0;
}
[dir="rtl"] & {
right: 0;
}
} }
.mdl-slider-background-lower-clear { .mdl-slider-background-lower-clear {

View file

@ -59,7 +59,6 @@
.mdl-switch__thumb { .mdl-switch__thumb {
background: #999; background: #999;
position: absolute; position: absolute;
left: 0;
top: -0.25em; top: -0.25em;
height: 1.44em; height: 1.44em;
width: 1.44em; width: 1.44em;
@ -72,6 +71,14 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
[dir="ltr"] & {
left: 0;
}
[dir="rtl"] & {
right: 0;
}
} }
.mdl-switch__input:checked + .mdl-switch__label + .mdl-switch__trackContainer > .mdl-switch__thumb { .mdl-switch__input:checked + .mdl-switch__label + .mdl-switch__trackContainer > .mdl-switch__thumb {

View file

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html class="preload"> <html class="preload" dir="ltr">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover"> <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
@ -112,13 +112,20 @@
.mainDrawerHandle { .mainDrawerHandle {
position: fixed; position: fixed;
top: 0; top: 0;
left: 0;
bottom: 0; bottom: 0;
z-index: 1; z-index: 1;
width: 0.8em; width: 0.8em;
padding-left: env(safe-area-inset-left); padding-left: env(safe-area-inset-left);
} }
[dir="ltr"] .mainDrawerHandle {
left: 0;
}
[dir="rtl"] .mainDrawerHandle {
left: 0;
}
@-webkit-keyframes fadein { @-webkit-keyframes fadein {
from { from {
opacity: 0; opacity: 0;
@ -163,7 +170,7 @@
} }
</style> </style>
</head> </head>
<body> <body dir="ltr">
<div class="backdropContainer"></div> <div class="backdropContainer"></div>
<div class="backgroundContainer"></div> <div class="backgroundContainer"></div>
<div class="mainDrawer hide"> <div class="mainDrawer hide">

View file

@ -7,6 +7,7 @@ import browser from '../../scripts/browser';
import dom from '../../scripts/dom'; import dom from '../../scripts/dom';
import './navdrawer.scss'; import './navdrawer.scss';
import '../../assets/css/scrollstyles.scss'; import '../../assets/css/scrollstyles.scss';
import globalize from '../../scripts/globalize';
function getTouches(e) { function getTouches(e) {
return e.changedTouches || e.targetTouches || e.touches; return e.changedTouches || e.targetTouches || e.touches;
@ -73,7 +74,10 @@ class NavDrawer {
const touch = touches[0] || {}; const touch = touches[0] || {};
const endX = touch.clientX || 0; const endX = touch.clientX || 0;
const endY = touch.clientY || 0; const endY = touch.clientY || 0;
const deltaX = endX - (this.menuTouchStartX || 0); let deltaX = endX - (this.menuTouchStartX || 0);
if (globalize.getIsRTL()) {
deltaX *= -1;
}
const deltaY = endY - (this.menuTouchStartY || 0); const deltaY = endY - (this.menuTouchStartY || 0);
this.setVelocity(deltaX); this.setVelocity(deltaX);
@ -106,7 +110,10 @@ class NavDrawer {
const touch = touches[0] || {}; const touch = touches[0] || {};
const endX = touch.clientX || 0; const endX = touch.clientX || 0;
const endY = touch.clientY || 0; const endY = touch.clientY || 0;
const deltaX = endX - (this.menuTouchStartX || 0); let deltaX = endX - (this.menuTouchStartX || 0);
if (globalize.getIsRTL()) {
deltaX *= -1;
}
const deltaY = endY - (this.menuTouchStartY || 0); const deltaY = endY - (this.menuTouchStartY || 0);
this.currentPos = deltaX; this.currentPos = deltaX;
this.checkMenuState(deltaX, deltaY); this.checkMenuState(deltaX, deltaY);
@ -161,7 +168,10 @@ class NavDrawer {
if (endX <= options.width && this.isVisible) { if (endX <= options.width && this.isVisible) {
this.countStart++; this.countStart++;
const deltaX = endX - (this.backgroundTouchStartX || 0); let deltaX = endX - (this.backgroundTouchStartX || 0);
if (globalize.getIsRTL()) {
deltaX *= -1;
}
if (this.countStart == 1) { if (this.countStart == 1) {
this.startPoint = deltaX; this.startPoint = deltaX;
@ -183,7 +193,10 @@ class NavDrawer {
const touches = getTouches(e); const touches = getTouches(e);
const touch = touches[0] || {}; const touch = touches[0] || {};
const endX = touch.clientX || 0; const endX = touch.clientX || 0;
const deltaX = endX - (this.backgroundTouchStartX || 0); let deltaX = endX - (this.backgroundTouchStartX || 0);
if (globalize.getIsRTL()) {
deltaX *= -1;
}
this.checkMenuState(deltaX); this.checkMenuState(deltaX);
this.countStart = 0; this.countStart = 0;
}; };
@ -193,6 +206,9 @@ class NavDrawer {
options.target.classList.add('touch-menu-la'); options.target.classList.add('touch-menu-la');
options.target.style.width = options.width + 'px'; options.target.style.width = options.width + 'px';
if (globalize.getIsRTL())
options.target.style.right = -options.width + 'px';
else
options.target.style.left = -options.width + 'px'; options.target.style.left = -options.width + 'px';
if (!options.disableMask) { if (!options.disableMask) {
@ -204,8 +220,9 @@ class NavDrawer {
animateToPosition(pos) { animateToPosition(pos) {
const options = this.options; const options = this.options;
const languageAwarePos = globalize.getIsRTL() ? -pos : pos;
requestAnimationFrame(function () { requestAnimationFrame(function () {
options.target.style.transform = pos ? 'translateX(' + pos + 'px)' : 'none'; options.target.style.transform = pos ? 'translateX(' + languageAwarePos + 'px)' : 'none';
}); });
} }

View file

@ -17,6 +17,13 @@
-webkit-transition: -webkit-transform ease-out 40ms, left ease-out 260ms; -webkit-transition: -webkit-transform ease-out 40ms, left ease-out 260ms;
-o-transition: transform ease-out 40ms, left ease-out 260ms; -o-transition: transform ease-out 40ms, left ease-out 260ms;
transition: transform ease-out 40ms, left ease-out 260ms; transition: transform ease-out 40ms, left ease-out 260ms;
[div="rtl"] & {
-webkit-transition: -webkit-transform ease-out 40ms, right ease-out 260ms;
-o-transition: transform ease-out 40ms, right ease-out 260ms;
transition: transform ease-out 40ms, right ease-out 260ms;
}
z-index: 1099; z-index: 1099;
} }
@ -24,6 +31,12 @@
-webkit-transition: -webkit-transform ease-out 240ms, left ease-out 260ms; -webkit-transition: -webkit-transform ease-out 240ms, left ease-out 260ms;
-o-transition: transform ease-out 240ms, left ease-out 260ms; -o-transition: transform ease-out 240ms, left ease-out 260ms;
transition: transform ease-out 240ms, left ease-out 260ms; transition: transform ease-out 240ms, left ease-out 260ms;
[div="rtl"] & {
-webkit-transition: -webkit-transform ease-out 240ms, right ease-out 260ms;
-o-transition: transform ease-out 240ms, right ease-out 260ms;
transition: transform ease-out 240ms, right ease-out 260ms;
}
} }
.drawer-open { .drawer-open {

View file

@ -9,6 +9,7 @@ import dom from '../scripts/dom';
import focusManager from '../components/focusManager'; import focusManager from '../components/focusManager';
import ResizeObserver from 'resize-observer-polyfill'; import ResizeObserver from 'resize-observer-polyfill';
import '../assets/css/scrollstyles.scss'; import '../assets/css/scrollstyles.scss';
import globalize from '../scripts/globalize';
/** /**
* Return type of the value. * Return type of the value.
@ -52,7 +53,14 @@ function disableOneEvent(event) {
* *
* @return {Number} * @return {Number}
*/ */
function within(number, min, max) { function within(number, num1, num2) {
if (num2 === undefined && globalize.getIsRTL()) {
return number > num1 ? num1 : number;
} else if (num2 === undefined) {
return number < num1 ? num1 : number;
}
const min = Math.min(num1, num2);
const max = Math.max(num1, num2);
if (number < min) { if (number < min) {
return min; return min;
} else if (number > max) { } else if (number > max) {
@ -173,6 +181,8 @@ const scrollerFactory = function (frame, options) {
// Set position limits & relativess // Set position limits & relativess
self._pos.end = Math.max(slideeSize - frameSize, 0); self._pos.end = Math.max(slideeSize - frameSize, 0);
if (globalize.getIsRTL())
self._pos.end *= -1;
} }
} }
@ -262,7 +272,9 @@ const scrollerFactory = function (frame, options) {
ensureSizeInfo(); ensureSizeInfo();
const pos = self._pos; const pos = self._pos;
if (layoutManager.tv) { if (layoutManager.tv && globalize.getIsRTL()) {
newPos = within(-newPos, pos.start);
} else if (layoutManager.tv) {
newPos = within(newPos, pos.start); newPos = within(newPos, pos.start);
} else { } else {
newPos = within(newPos, pos.start, pos.end); newPos = within(newPos, pos.start, pos.end);
@ -354,7 +366,12 @@ const scrollerFactory = function (frame, options) {
const slideeOffset = getBoundingClientRect(scrollElement); const slideeOffset = getBoundingClientRect(scrollElement);
const itemOffset = getBoundingClientRect(item); const itemOffset = getBoundingClientRect(item);
let offset = o.horizontal ? itemOffset.left - slideeOffset.left : itemOffset.top - slideeOffset.top; let horizontalOffset = itemOffset.left - slideeOffset.left;
if (globalize.getIsRTL()) {
horizontalOffset = slideeOffset.right - itemOffset.right;
}
let offset = o.horizontal ? horizontalOffset : itemOffset.top - slideeOffset.top;
let size = o.horizontal ? itemOffset.width : itemOffset.height; let size = o.horizontal ? itemOffset.width : itemOffset.height;
if (!size && size !== 0) { if (!size && size !== 0) {
@ -375,17 +392,21 @@ const scrollerFactory = function (frame, options) {
ensureSizeInfo(); ensureSizeInfo();
const currentStart = self._pos.cur; const currentStart = self._pos.cur;
const currentEnd = currentStart + frameSize; let currentEnd = currentStart + frameSize;
if (globalize.getIsRTL()) {
currentEnd = currentStart - frameSize;
}
console.debug('offset:' + offset + ' currentStart:' + currentStart + ' currentEnd:' + currentEnd); console.debug('offset:' + offset + ' currentStart:' + currentStart + ' currentEnd:' + currentEnd);
const isVisible = offset >= currentStart && (offset + size) <= currentEnd; const isVisible = offset >= Math.min(currentStart, currentEnd)
&& (globalize.getIsRTL() ? (offset - size) : (offset + size)) <= Math.max(currentStart, currentEnd);
return { return {
start: offset, start: offset,
center: offset + centerOffset - (frameSize / 2) + (size / 2), center: offset + centerOffset - (frameSize / 2) + (size / 2),
end: offset - frameSize + size, end: offset - frameSize + size,
size: size, size,
isVisible: isVisible isVisible
}; };
}; };

View file

@ -1347,6 +1347,7 @@ function tryRemoveElement(elem) {
loading.show(); loading.show();
const dlg = document.createElement('div'); const dlg = document.createElement('div');
dlg.setAttribute('dir', 'ltr');
dlg.classList.add('videoPlayerContainer'); dlg.classList.add('videoPlayerContainer');

View file

@ -84,7 +84,7 @@ import globalize from './globalize';
hours = Math.floor(hours); hours = Math.floor(hours);
if (hours) { if (hours) {
parts.push(hours); parts.push(hours.toLocaleString(globalize.getCurrentDateTimeLocale()));
} }
ticks -= (hours * ticksPerHour); ticks -= (hours * ticksPerHour);
@ -95,7 +95,9 @@ import globalize from './globalize';
ticks -= (minutes * ticksPerMinute); ticks -= (minutes * ticksPerMinute);
if (minutes < 10 && hours) { if (minutes < 10 && hours) {
minutes = '0' + minutes; minutes = (0).toLocaleString(globalize.getCurrentDateTimeLocale()) + minutes.toLocaleString(globalize.getCurrentDateTimeLocale());
} else {
minutes = minutes.toLocaleString(globalize.getCurrentDateTimeLocale());
} }
parts.push(minutes); parts.push(minutes);
@ -103,7 +105,9 @@ import globalize from './globalize';
seconds = Math.floor(seconds); seconds = Math.floor(seconds);
if (seconds < 10) { if (seconds < 10) {
seconds = '0' + seconds; seconds = (0).toLocaleString(globalize.getCurrentDateTimeLocale()) + seconds.toLocaleString(globalize.getCurrentDateTimeLocale());
} else {
seconds = seconds.toLocaleString(globalize.getCurrentDateTimeLocale());
} }
parts.push(seconds); parts.push(seconds);

View file

@ -3,13 +3,20 @@ import isEmpty from 'lodash-es/isEmpty';
import { currentSettings as userSettings } from './settings/userSettings'; import { currentSettings as userSettings } from './settings/userSettings';
import Events from '../utils/events.ts'; import Events from '../utils/events.ts';
const Direction = {
rtl: 'rtl',
ltr: 'ltr'
};
/* eslint-disable indent */ /* eslint-disable indent */
const fallbackCulture = 'en-us'; const fallbackCulture = 'en-us';
const RTL_LANGS = ['ar', 'fa', 'ur', 'he'];
const allTranslations = {}; const allTranslations = {};
let currentCulture; let currentCulture;
let currentDateTimeCulture; let currentDateTimeCulture;
let isRTL = false;
export function getCurrentLocale() { export function getCurrentLocale() {
return currentCulture; return currentCulture;
@ -38,6 +45,37 @@ import Events from '../utils/events.ts';
return fallbackCulture; return fallbackCulture;
} }
export function getIsRTL() {
return isRTL;
}
function checkAndProcessDir(culture) {
isRTL = false;
console.log(culture);
for (const lang of RTL_LANGS) {
if (culture.includes(lang)) {
isRTL = true;
break;
}
}
setDocumentDirection(isRTL ? Direction.rtl : Direction.ltr);
}
function setDocumentDirection(direction) {
document.getElementsByTagName('body')[0].setAttribute('dir', direction);
document.getElementsByTagName('html')[0].setAttribute('dir', direction);
if (direction === Direction.rtl)
import('../styles/rtl.scss');
}
export function getIsElementRTL(element) {
if (window.getComputedStyle) { // all browsers
return window.getComputedStyle(element, null).getPropertyValue('direction') == 'rtl';
}
return element.currentStyle.direction == 'rtl';
}
export function updateCurrentCulture() { export function updateCurrentCulture() {
let culture; let culture;
try { try {
@ -46,6 +84,7 @@ import Events from '../utils/events.ts';
console.error('no language set in user settings'); console.error('no language set in user settings');
} }
culture = culture || getDefaultLanguage(); culture = culture || getDefaultLanguage();
checkAndProcessDir(culture);
currentCulture = normalizeLocaleName(culture); currentCulture = normalizeLocaleName(culture);
@ -200,7 +239,7 @@ import Events from '../utils/events.ts';
export function translate(key) { export function translate(key) {
let val = translateKey(key); let val = translateKey(key);
for (let i = 1; i < arguments.length; i++) { for (let i = 1; i < arguments.length; i++) {
val = replaceAll(val, '{' + (i - 1) + '}', arguments[i]); val = replaceAll(val, '{' + (i - 1) + '}', arguments[i].toLocaleString(currentCulture));
} }
return val; return val;
} }
@ -257,7 +296,9 @@ export default {
getCurrentLocale, getCurrentLocale,
getCurrentDateTimeLocale, getCurrentDateTimeLocale,
register, register,
updateCurrentCulture updateCurrentCulture,
getIsRTL,
getIsElementRTL
}; };
/* eslint-enable indent */ /* eslint-enable indent */

View file

@ -88,7 +88,7 @@ export function getQueryPagingHtml (options) {
if (showControls) { if (showControls) {
html += '<span style="vertical-align:middle;">'; html += '<span style="vertical-align:middle;">';
html += globalize.translate('ListPaging', (totalRecordCount ? startIndex + 1 : 0), recordsEnd, totalRecordCount); html += globalize.translate('ListPaging', totalRecordCount ? startIndex + 1 : 0, recordsEnd, totalRecordCount);
html += '</span>'; html += '</span>';
} }

18
src/styles/rtl.scss Normal file
View file

@ -0,0 +1,18 @@
.chevron_right,
.chevron_left,
.arrow_back,
.arrow_forward,
.playlist_add,
.shuffle,
.input,
.dvr,
.shopping_cart,
.vpn_key,
.volume_up,
.volume_off,
.message,
.clear_all {
[dir='rtl'] & {
transform: scale(-1, 1);
}
}