Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,31 @@
</div>
<global-search></global-search>
<div class="header-actions">
<clr-dropdown class="dropdown-lang dropdown bottom-left" *ngIf="!isIntegrationMode">
<clr-dropdown class="dropdown-locale dropdown bottom-left" *ngIf="!isIntegrationMode">
<button class="nav-icon nav-icon-width" clrDropdownToggle>
<clr-icon shape="world" class="icon-left"></clr-icon>
<span class="currentLang">{{currentLang}}</span>
<span class="currentLocale">{{currentLang}}</span>
<clr-icon size="10" shape="caret down"></clr-icon>
</button>
<clr-dropdown-menu *clrIfOpen>
<a href="javascript:void(0)" clrDropdownItem (click)='switchLanguage("en-us")' [class.lang-selected]='matchLang("en-us")'>English</a>
<a href="javascript:void(0)" clrDropdownItem (click)='switchLanguage("zh-cn")' [class.lang-selected]='matchLang("zh-cn")'>中文简体</a>
<a href="javascript:void(0)" clrDropdownItem (click)='switchLanguage("zh-tw")' [class.lang-selected]='matchLang("zh-tw")'>中文繁體</a>
<a href="javascript:void(0)" clrDropdownItem (click)='switchLanguage("es-es")' [class.lang-selected]='matchLang("es-es")'>Español</a>
<a href="javascript:void(0)" clrDropdownItem (click)='switchLanguage("fr-fr")' [class.lang-selected]='matchLang("fr-fr")'>Français</a>
<a href="javascript:void(0)" clrDropdownItem (click)='switchLanguage("pt-br")' [class.lang-selected]='matchLang("pt-br")'>Português do Brasil</a>
<a href="javascript:void(0)" clrDropdownItem (click)='switchLanguage("tr-tr")' [class.lang-selected]='matchLang("tr-tr")'>Türkçe</a>
<a href="javascript:void(0)" clrDropdownItem (click)='switchLanguage("de-de")' [class.lang-selected]='matchLang("de-de")'>Deutsch</a>
<a href="javascript:void(0)" clrDropdownItem (click)='switchLanguage("en-us")' [class.locale-selected]='matchLang("en-us")'>English</a>
<a href="javascript:void(0)" clrDropdownItem (click)='switchLanguage("zh-cn")' [class.locale-selected]='matchLang("zh-cn")'>中文简体</a>
<a href="javascript:void(0)" clrDropdownItem (click)='switchLanguage("zh-tw")' [class.locale-selected]='matchLang("zh-tw")'>中文繁體</a>
<a href="javascript:void(0)" clrDropdownItem (click)='switchLanguage("es-es")' [class.locale-selected]='matchLang("es-es")'>Español</a>
<a href="javascript:void(0)" clrDropdownItem (click)='switchLanguage("fr-fr")' [class.locale-selected]='matchLang("fr-fr")'>Français</a>
<a href="javascript:void(0)" clrDropdownItem (click)='switchLanguage("pt-br")' [class.locale-selected]='matchLang("pt-br")'>Português do Brasil</a>
<a href="javascript:void(0)" clrDropdownItem (click)='switchLanguage("tr-tr")' [class.locale-selected]='matchLang("tr-tr")'>Türkçe</a>
<a href="javascript:void(0)" clrDropdownItem (click)='switchLanguage("de-de")' [class.locale-selected]='matchLang("de-de")'>Deutsch</a>
</clr-dropdown-menu>
</clr-dropdown>
<clr-dropdown class="dropdown-locale dropdown bottom-left" *ngIf="!isIntegrationMode">
<button class="nav-icon nav-icon-width" clrDropdownToggle>
<clr-icon shape="date" class="icon-left"></clr-icon>
<span class="currentLocale">{{currentDatetimeRendering | translate}}</span>
<clr-icon size="10" shape="caret down"></clr-icon>
</button>
<clr-dropdown-menu *clrIfOpen>
<a *ngFor="let rendering of guiDatetimeRenderings" href="javascript:void(0)" clrDropdownItem (click)='switchDatetimeRendering(rendering[0])' [class.locale-selected]='matchDatetimeRendering(rendering[0])'>{{rendering[1] | translate}}</a>
</clr-dropdown-menu>
</clr-dropdown>
<div class="nav-divider"></div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
height: 24px;
}

.lang-selected {
.locale-selected {
font-weight: bold;
}

Expand Down Expand Up @@ -48,7 +48,7 @@
.icon-left {
left: -8px;
}
.currentLang {
.currentLocale {
padding-right: 40px;
}
}
Expand All @@ -65,7 +65,7 @@
.dropdown-item {
outline: none;
}
.dropdown-lang {
.dropdown-locale {
padding-right: 0.5rem;
}
.user-down {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,16 @@ import { MessageHandlerService } from '../../services/message-handler.service';
import { SkinableConfig } from "../../../services/skinable-config.service";
import {
CommonRoutes,
DATETIME_RENDERINGS,
DatetimeRendering,
DEFAULT_DATETIME_RENDERING_LOCALSTORAGE_KEY,
DEFAULT_LANG_LOCALSTORAGE_KEY,
DefaultDatetimeRendering,
DeFaultLang,
languageNames,
} from "../../entities/shared.const";
import { CustomStyle, HAS_STYLE_MODE, StyleMode } from "../../../services/theme";
import { getDatetimeRendering } from '../../units/shared.utils';


@Component({
Expand All @@ -42,6 +47,8 @@ export class NavigatorComponent implements OnInit {
@Output() showDialogModalAction = new EventEmitter<ModalEvent>();

selectedLang: string = DeFaultLang;
selectedDatetimeRendering: DatetimeRendering = DefaultDatetimeRendering;
readonly guiDatetimeRenderings = Object.entries(DATETIME_RENDERINGS);
appTitle: string = 'APP_TITLE.HARBOR';
customStyle: CustomStyle;
constructor(
Expand All @@ -59,6 +66,7 @@ export class NavigatorComponent implements OnInit {
// custom skin
this.customStyle = this.skinableConfig.getSkinConfig();
this.selectedLang = this.translate.currentLang;
this.selectedDatetimeRendering = getDatetimeRendering();
if (this.appConfigService.isIntegrationMode()) {
this.appTitle = 'APP_TITLE.VIC';
}
Expand All @@ -80,6 +88,10 @@ export class NavigatorComponent implements OnInit {
return languageNames[this.selectedLang];
}

public get currentDatetimeRendering(): string {
return DATETIME_RENDERINGS[this.selectedDatetimeRendering];
}

public get admiralLink(): string {
return this.appConfigService.getAdmiralEndpoint(window.location.href);
}
Expand Down Expand Up @@ -107,6 +119,10 @@ export class NavigatorComponent implements OnInit {
return lang.trim() === this.selectedLang;
}

matchDatetimeRendering(datetime: DatetimeRendering): boolean {
return datetime === this.selectedDatetimeRendering;
}

// Open the account setting dialog
openAccountSettingsModal(): void {
this.showAccountSettingsModal.emit({
Expand Down Expand Up @@ -154,6 +170,11 @@ export class NavigatorComponent implements OnInit {
this.translate.use(lang).subscribe(() => window.location.reload());
}

switchDatetimeRendering(datetime: DatetimeRendering): void {
this.selectedDatetimeRendering = datetime;
localStorage.setItem(DEFAULT_DATETIME_RENDERING_LOCALSTORAGE_KEY, datetime);
}

// Handle the home action
homeAction(): void {
if (this.session.getCurrentUser() != null) {
Expand Down
11 changes: 11 additions & 0 deletions src/portal/src/app/shared/entities/shared.const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,17 @@ export const languageNames = {
*/
export const DEFAULT_LANG_LOCALSTORAGE_KEY = 'harbor-lang';

export type DatetimeRendering = keyof typeof DATETIME_RENDERINGS;
export const DATETIME_RENDERINGS = {
"locale-default": "TOP_NAV.DATETIME_RENDERING_DEFAULT",
"iso-8601": "ISO 8601",
} as const;
export const DefaultDatetimeRendering = "locale-default";
/**
* The default cookie key used to store current used datetime rendering preference.
*/
export const DEFAULT_DATETIME_RENDERING_LOCALSTORAGE_KEY = 'harbor-datetime-rendering';

export const AdmiralQueryParamKey = "admiral_redirect_url";

export const HarborQueryParamKey = "harbor_redirect_url";
Expand Down
30 changes: 28 additions & 2 deletions src/portal/src/app/shared/pipes/harbor-datetime.pipe.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { Pipe, PipeTransform } from '@angular/core';
import { DatePipe } from "@angular/common";
import { DEFAULT_LANG_LOCALSTORAGE_KEY, DeFaultLang } from "../entities/shared.const";
import { getDatetimeRendering } from "../units/shared.utils";
import { DEFAULT_LANG_LOCALSTORAGE_KEY, DeFaultLang, DatetimeRendering } from "../entities/shared.const";

const baseTimeLine: Date = new Date('1970-1-1');

const formatTransformers: Record<DatetimeRendering, (format: string) => string> = {
"iso-8601": asISO8601,
"locale-default": format => format,
} as const;

@Pipe({
name: 'harborDatetime',
pure: false
Expand All @@ -18,7 +24,27 @@ export class HarborDatetimePipe implements PipeTransform {
if (value && value <= baseTimeLine) {// invalid date
return '-';
}
const formatTransformer = formatTransformers[getDatetimeRendering()];
// default format medium
return new DatePipe(lang).transform(value, format ? format : 'medium');
return new DatePipe(lang).transform(value, formatTransformer(format ? format : 'medium'));
}
}

function asISO8601<Format extends string>(format: Format) {
switch (format) {
// https://angular.io/api/common/DatePipe#pre-defined-format-options
case "short": return "yyyy-MM-dd, HH:mm";
case "medium": return "yyyy-MM-dd, HH:mm:ss";
case "long": return "yyyy-MM-dd, HH:mm:ss z";
case "full": return "EEEE yyyy-MM-dd, HH:mm:ss zzzz";
case "shortDate": return "yyyy-MM-dd";
case "mediumDate": return "yyyy-MM-dd";
case "longDate": return "yyyy-MM-dd z";
case "fullDate": return "EEEE yyyy-MM-dd zzzz";
case "shortTime": return "HH:mm";
case "mediumTime": return "HH:mm:ss";
case "longTime": return "HH:mm:ss z";
case "fullTime": return "HH:mm:ss zzzz";
default: return format;
}
}
27 changes: 26 additions & 1 deletion src/portal/src/app/shared/units/shared.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,15 @@
// limitations under the License.
import { NgForm } from '@angular/forms';
import { MessageService } from '../components/global-message/message.service';
import { AlertType, httpStatusCode } from "../entities/shared.const";
import {
AlertType,
DatetimeRendering,
DATETIME_RENDERINGS,
DEFAULT_DATETIME_RENDERING_LOCALSTORAGE_KEY,
DefaultDatetimeRendering,
httpStatusCode
} from "../entities/shared.const";


/**
* To check if form is empty
Expand Down Expand Up @@ -194,3 +202,20 @@ export const errorHandler = function (error: any): string {
}
}
};

/**
* Gets the datetime rendering setting saved by the user, or the default setting if no valid saved value is found.
*/
export function getDatetimeRendering(): DatetimeRendering {
const savedDatetimeRendering = localStorage && localStorage.getItem(DEFAULT_DATETIME_RENDERING_LOCALSTORAGE_KEY);
if (isDatetimeRendering(savedDatetimeRendering)) {
return savedDatetimeRendering;
} else {
console.warn(`Invalid saved datetime rendering setting ${JSON.stringify(savedDatetimeRendering)}; defaulting to ${JSON.stringify(DefaultDatetimeRendering)}.`);
return DefaultDatetimeRendering;
}
}

function isDatetimeRendering(x: unknown): x is DatetimeRendering {
return Object.keys(DATETIME_RENDERINGS).some(k => k === x);
}
3 changes: 3 additions & 0 deletions src/portal/src/i18n/lang/de-de-lang.json
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@
"PLACEHOLDER": "Suche {{param}}...",
"PLACEHOLDER_VIC": "Suche Registry..."
},
"TOP_NAV": {
"DATETIME_RENDERING_DEFAULT": "Standard"
},
"SIDE_NAV": {
"DASHBOARD": "Übersicht",
"PROJECTS": "Projekte",
Expand Down
3 changes: 3 additions & 0 deletions src/portal/src/i18n/lang/en-us-lang.json
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@
"PLACEHOLDER": "Search {{param}}...",
"PLACEHOLDER_VIC": "Search Registry..."
},
"TOP_NAV": {
"DATETIME_RENDERING_DEFAULT": "Default"
},
"SIDE_NAV": {
"DASHBOARD": "Dashboard",
"PROJECTS": "Projects",
Expand Down
3 changes: 3 additions & 0 deletions src/portal/src/i18n/lang/es-es-lang.json
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@
"PLACEHOLDER": "Buscar en {{param}}...",
"PLACEHOLDER_VIC": "Buscar en el registro..."
},
"TOP_NAV": {
"DATETIME_RENDERING_DEFAULT": "Default"
},
"SIDE_NAV": {
"DASHBOARD": "Panel",
"PROJECTS": "Proyectos",
Expand Down
3 changes: 3 additions & 0 deletions src/portal/src/i18n/lang/fr-fr-lang.json
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,9 @@
"PLACEHOLDER": "Recherche {{param}}...",
"PLACEHOLDER_VIC": "Recherche dans le registre..."
},
"TOP_NAV": {
"DATETIME_RENDERING_DEFAULT": "Défaut"
},
"SIDE_NAV": {
"DASHBOARD": "Tableau de bord",
"PROJECTS": "Projets",
Expand Down
3 changes: 3 additions & 0 deletions src/portal/src/i18n/lang/pt-br-lang.json
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,9 @@
"PLACEHOLDER": "Busca {{param}}...",
"PLACEHOLDER_VIC": "Busca de registro..."
},
"TOP_NAV": {
"DATETIME_RENDERING_DEFAULT": "Padrão"
},
"SIDE_NAV": {
"DASHBOARD": "Painel de controle",
"PROJECTS": "Projetos",
Expand Down
3 changes: 3 additions & 0 deletions src/portal/src/i18n/lang/tr-tr-lang.json
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@
"PLACEHOLDER": "Ara {{param}}...",
"PLACEHOLDER_VIC": "Arama Kaydı..."
},
"TOP_NAV": {
"DATETIME_RENDERING_DEFAULT": "Default"
},
"SIDE_NAV": {
"DASHBOARD": "Kontrol Paneli",
"PROJECTS": "Projeler",
Expand Down
3 changes: 3 additions & 0 deletions src/portal/src/i18n/lang/zh-cn-lang.json
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,9 @@
"PLACEHOLDER": "搜索 {{param}}...",
"PLACEHOLDER_VIC": "搜索 Registry..."
},
"TOP_NAV": {
"DATETIME_RENDERING_DEFAULT": "默认"
},
"SIDE_NAV": {
"DASHBOARD": "仪表板",
"PROJECTS": "项目",
Expand Down
3 changes: 3 additions & 0 deletions src/portal/src/i18n/lang/zh-tw-lang.json
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,9 @@
"PLACEHOLDER": "搜索{{param}}...",
"PLACEHOLDER_VIC": "搜索Registry..."
},
"TOP_NAV": {
"DATETIME_RENDERING_DEFAULT": "默認"
},
"SIDE_NAV":{
"DASHBOARD": "儀表板",
"PROJECTS": "項目",
Expand Down