@sinequa/atomic-angular 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/features/settings/i18n/de.json +41 -0
- package/features/settings/i18n/en.json +41 -0
- package/features/settings/i18n/fr.json +41 -0
- package/features/user-profile/i18n/de.json +0 -1
- package/features/user-profile/i18n/en.json +0 -1
- package/features/user-profile/i18n/fr.json +0 -1
- package/fesm2022/sinequa-atomic-angular.mjs +913 -266
- package/fesm2022/sinequa-atomic-angular.mjs.map +1 -1
- package/package.json +5 -1
- package/types/sinequa-atomic-angular-document-workspaces.d.ts +9 -9
- package/types/sinequa-atomic-angular.d.ts +183 -121
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as i0 from "@angular/core";
|
|
2
2
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, DOCUMENT, DestroyRef, Directive, ElementRef, EnvironmentInjector, HostBinding, HostListener, Inject, Injectable, InjectionToken, Injector, LOCALE_ID, Optional, Pipe, Service, TemplateRef, ViewEncapsulation, afterNextRender, afterRenderEffect, assertInInjectionContext, booleanAttribute, computed, contentChild, contentChildren, effect, inject, input, isDevMode, linkedSignal, makeEnvironmentProviders, model, numberAttribute, output, provideEnvironmentInitializer, resource, runInInjectionContext, signal, untracked, viewChild, viewChildren } from "@angular/core";
|
|
3
|
-
import { BehaviorSubject, EMPTY, Subject, Subscription, catchError, debounceTime, filter, firstValueFrom, from, fromEvent, map, of, shareReplay, switchMap, tap, throwError } from "rxjs";
|
|
3
|
+
import { BehaviorSubject, EMPTY, Subject, Subscription, catchError, debounceTime, filter, firstValueFrom, from, fromEvent, map, of, scan, shareReplay, switchMap, tap, throwError } from "rxjs";
|
|
4
4
|
import { TranslocoPipe, TranslocoService, provideTranslocoScope } from "@jsverse/transloco";
|
|
5
|
-
import { ArrowDownZaIcon, ArrowLeftIcon, ArrowRightFromBracketIcon, ArrowRightIcon, ArrowUpAzIcon, ArrowUpRightFromSquareIcon, ArrowsMaximizeIcon, AutocompleteComponent, AvatarComponent, AvatarFallbackComponent, AvatarImageComponent, BadgeComponent, BarsIcon, BellIcon, BookmarkIcon, BreakpointObserverService, ButtonComponent, CalendarDayIcon, CheckIcon, CheckboxComponent, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronsLeftIcon, ChevronsRightIcon, CircleCheckIcon, CommentIcon, CommentsIcon, DIALOG_REF, DesktopIcon, DialogBodyComponent, DialogComponent, DialogContentComponent, DialogDescriptionComponent, DialogFooterComponent, DialogHeaderComponent, DialogTitleComponent, DropdownDirective, EditIcon, EllipsisIcon, EllipsisVerticalIcon, EyeIcon, EyeSlashIcon, FileHTMLIcon, FileIcon, FileOutputIcon, FileSpreadsheetIcon, FileTextIcon, FilterIcon, FilterXIcon, FlagEnglishIcon, FlagFrenchIcon, FlagGermanIcon, FlashlightIcon, FolderIcon, FolderOpenIcon, FrownIcon, GearIcon, GitForkIcon, GlobeIcon, HighlighterIcon, HistoryIcon, HomeIcon, ImageIcon, InboxIcon, InfoCircleIcon, InputComponent, InputControlDirective, InputIconRightDirective, KbdComponent, KbdGroupComponent, KeyIcon, LanguageIcon, LightbulbIcon, LightbulbSlashIcon, LinkComponent, ListComponent, ListFilterIcon, ListItemActionComponent, ListItemAvatarDirective, ListItemComponent, ListItemLeftDirective, ListItemTextComponent, LoadingCircleIcon, MagnifyingGlassIcon, MagnifyingGlassMinusIcon, MagnifyingGlassPlusIcon, MenuComponent, MenuContentComponent, MenuItemComponent, MenuTriggerDirective, MoonIcon, PaletteIcon, PaperclipIcon, PenToSquareIcon, PlusIcon, PopoverDirective, PresentationIcon, QuestionCircleIcon, RadioComponent, RobotIcon, SaveIcon, SearchBarComponent, SearchInputComponent, SelectComponent, SelectTriggerDirective, SendIcon, SheetBodyComponent, SheetComponent, SheetHeaderComponent, SlashIcon, SlidersIcon, SparklesIcon, SpinnerIcon, SquareCheckIcon, SquareIcon, SquareMinusIcon, StarIcon, SunBrightIcon, SwitchComponent, SyncIcon, TabComponent, TabListComponent, TabPanelComponent, TabsComponent, TagComponent, TagIcon, TagsIcon, TextareaComponent, TextareaControlDirective, ThumbsDownIcon, ThumbsUpIcon, ToggleComponent, ToggleGroupComponent, ToggleItemComponent, ToggleLeftIcon, ToggleRightIcon, TooltipDirective, TrashCanIcon, TrashIcon, TreeComponent, TreeItemComponent, TreeItemExpanderComponent, TreeItemGroupComponent, TriangleAlertIcon, UndoIcon, UserIcon, UserSecretIcon, VideoIcon, XMarkIcon, createCallable, injectCallRef } from "@sinequa/galactik";
|
|
5
|
+
import { ArrowDownZaIcon, ArrowLeftIcon, ArrowRightFromBracketIcon, ArrowRightIcon, ArrowUpAzIcon, ArrowUpRightFromSquareIcon, ArrowsMaximizeIcon, AutocompleteComponent, AvatarComponent, AvatarFallbackComponent, AvatarImageComponent, BadgeComponent, BarsIcon, BellIcon, BookmarkIcon, BreakpointObserverService, ButtonComponent, CalendarDayIcon, CheckIcon, CheckboxComponent, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronsLeftIcon, ChevronsRightIcon, CircleCheckIcon, CommentIcon, CommentsIcon, DIALOG_REF, DesktopIcon, DialogBodyComponent, DialogComponent, DialogContentComponent, DialogDescriptionComponent, DialogFooterComponent, DialogHeaderComponent, DialogTitleComponent, DropdownDirective, EditIcon, EllipsisIcon, EllipsisVerticalIcon, EyeIcon, EyeSlashIcon, FileHTMLIcon, FileIcon, FileOutputIcon, FileSpreadsheetIcon, FileTextIcon, FilterIcon, FilterXIcon, FlagEnglishIcon, FlagFrenchIcon, FlagGermanIcon, FlashlightIcon, FolderIcon, FolderOpenIcon, FrownIcon, GearIcon, GitForkIcon, GlobeIcon, HighlighterIcon, HistoryIcon, HomeIcon, ImageIcon, InboxIcon, InfoCircleIcon, InputComponent, InputControlDirective, InputIconRightDirective, KbdComponent, KbdGroupComponent, KeyIcon, KeyboardIcon, LanguageIcon, LightbulbIcon, LightbulbSlashIcon, LinkComponent, ListComponent, ListFilterIcon, ListItemActionComponent, ListItemAvatarDirective, ListItemComponent, ListItemLeftDirective, ListItemTextComponent, LoadingCircleIcon, MagnifyingGlassIcon, MagnifyingGlassMinusIcon, MagnifyingGlassPlusIcon, MenuComponent, MenuContentComponent, MenuItemComponent, MenuTriggerDirective, MoonIcon, PaletteIcon, PaperclipIcon, PenToSquareIcon, PlusIcon, PopoverDirective, PresentationIcon, QuestionCircleIcon, RadioComponent, RobotIcon, SaveIcon, SearchBarComponent, SearchInputComponent, SelectComponent, SelectTriggerDirective, SendIcon, SheetBodyComponent, SheetComponent, SheetHeaderComponent, SlashIcon, SlidersIcon, SparklesIcon, SpinnerIcon, SquareCheckIcon, SquareIcon, SquareMinusIcon, StarIcon, SunBrightIcon, SwitchComponent, SyncIcon, TabComponent, TabListComponent, TabPanelComponent, TabsComponent, TagComponent, TagIcon, TagsIcon, TextareaComponent, TextareaControlDirective, ThumbsDownIcon, ThumbsUpIcon, ToggleComponent, ToggleGroupComponent, ToggleItemComponent, ToggleLeftIcon, ToggleRightIcon, TooltipDirective, TrashCanIcon, TrashIcon, TreeComponent, TreeItemComponent, TreeItemExpanderComponent, TreeItemGroupComponent, TriangleAlertIcon, UndoIcon, UserIcon, UserSecretIcon, VideoIcon, XMarkIcon, createCallable, injectCallRef } from "@sinequa/galactik";
|
|
6
6
|
import { withDevtools } from "@angular-architects/ngrx-toolkit";
|
|
7
7
|
import { getState, patchState, signalStore, signalStoreFeature, withComputed, withMethods, withState } from "@ngrx/signals";
|
|
8
8
|
import { ActivatedRoute, NavigationEnd, Router, RouterLink, RouterModule } from "@angular/router";
|
|
@@ -20658,9 +20658,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
20658
20658
|
}] }
|
|
20659
20659
|
});
|
|
20660
20660
|
const FeatureFlags = createCallable(FeatureFlagsDialogComponent);
|
|
20661
|
-
var
|
|
20662
|
-
call = injectCallRef();
|
|
20663
|
-
dialog = viewChild(DialogComponent, ...ngDevMode ? [{ debugName: "dialog" }] : /* istanbul ignore next */ []);
|
|
20661
|
+
var KeyboardShortcutsListComponent = class KeyboardShortcutsListComponent {
|
|
20664
20662
|
keyboard = inject(KeyboardShortcutsService);
|
|
20665
20663
|
groups = computed(() => {
|
|
20666
20664
|
const listed = this.keyboard.shortcuts().filter((shortcut) => !shortcut.hidden && shortcut.labelKey);
|
|
@@ -20675,6 +20673,146 @@ var KeyboardShortcutsDialogComponent = class KeyboardShortcutsDialogComponent {
|
|
|
20675
20673
|
items
|
|
20676
20674
|
}));
|
|
20677
20675
|
}, ...ngDevMode ? [{ debugName: "groups" }] : /* istanbul ignore next */ []);
|
|
20676
|
+
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
20677
|
+
minVersion: "12.0.0",
|
|
20678
|
+
version: "22.2.1",
|
|
20679
|
+
ngImport: i0,
|
|
20680
|
+
type: KeyboardShortcutsListComponent,
|
|
20681
|
+
deps: [],
|
|
20682
|
+
target: i0.ɵɵFactoryTarget.Component
|
|
20683
|
+
});
|
|
20684
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({
|
|
20685
|
+
minVersion: "17.0.0",
|
|
20686
|
+
version: "22.2.1",
|
|
20687
|
+
type: KeyboardShortcutsListComponent,
|
|
20688
|
+
isStandalone: true,
|
|
20689
|
+
selector: "keyboard-shortcuts-list",
|
|
20690
|
+
providers: [provideTranslocoScope("dialogs")],
|
|
20691
|
+
ngImport: i0,
|
|
20692
|
+
template: `
|
|
20693
|
+
@if (groups().length === 0) {
|
|
20694
|
+
<p>{{ "dialogs.keyboardShortcuts.empty" | transloco }}</p>
|
|
20695
|
+
}
|
|
20696
|
+
|
|
20697
|
+
@for (group of groups(); track group.titleKey ?? "") {
|
|
20698
|
+
@if (group.titleKey) {
|
|
20699
|
+
<h3 class="mt-(--spacing-md) mb-(--spacing-4xs) text-(length:--size-lg) font-semibold first:mt-0">{{ group.titleKey | transloco }}</h3>
|
|
20700
|
+
}
|
|
20701
|
+
<dl>
|
|
20702
|
+
@for (shortcut of group.items; track shortcut) {
|
|
20703
|
+
<div class="flex items-center justify-between gap-(--spacing-md) py-(--spacing-4xs)">
|
|
20704
|
+
<dt>{{ shortcut.labelKey! | transloco }}</dt>
|
|
20705
|
+
<dd class="flex items-center gap-(--spacing-3xs)">
|
|
20706
|
+
<!-- One group per combination: a shortcut may answer to several (Alt+0 and
|
|
20707
|
+
Shift+?). The group carries the whole combination as its name and the keys
|
|
20708
|
+
are hidden, otherwise a screen reader reads out every key and separator as
|
|
20709
|
+
a separate item. -->
|
|
20710
|
+
<!-- comboIndex is captured explicitly: inside the nested loop $index would
|
|
20711
|
+
refer to the keys, not to the combination.
|
|
20712
|
+
(No backticks in this comment: the template is a template literal.) -->
|
|
20713
|
+
@for (aria of shortcut.aria; track aria; let first = $first, comboIndex = $index) {
|
|
20714
|
+
@if (!first) {
|
|
20715
|
+
<span aria-hidden="true">{{ "dialogs.keyboardShortcuts.or" | transloco }}</span>
|
|
20716
|
+
}
|
|
20717
|
+
<KbdGroup [attr.aria-label]="aria">
|
|
20718
|
+
@for (part of shortcut.displayParts[comboIndex]; track $index; let firstPart = $first) {
|
|
20719
|
+
@if (!firstPart) {
|
|
20720
|
+
<span aria-hidden="true">+</span>
|
|
20721
|
+
}
|
|
20722
|
+
<kbd aria-hidden="true">{{ part }}</kbd>
|
|
20723
|
+
}
|
|
20724
|
+
</KbdGroup>
|
|
20725
|
+
}
|
|
20726
|
+
</dd>
|
|
20727
|
+
</div>
|
|
20728
|
+
}
|
|
20729
|
+
</dl>
|
|
20730
|
+
}
|
|
20731
|
+
`,
|
|
20732
|
+
isInline: true,
|
|
20733
|
+
dependencies: [
|
|
20734
|
+
{
|
|
20735
|
+
kind: "directive",
|
|
20736
|
+
type: KbdComponent,
|
|
20737
|
+
selector: "kbd, Kbd",
|
|
20738
|
+
inputs: ["class", "size"]
|
|
20739
|
+
},
|
|
20740
|
+
{
|
|
20741
|
+
kind: "directive",
|
|
20742
|
+
type: KbdGroupComponent,
|
|
20743
|
+
selector: "kbd-group, kbdgroup, KbdGroup"
|
|
20744
|
+
},
|
|
20745
|
+
{
|
|
20746
|
+
kind: "pipe",
|
|
20747
|
+
type: TranslocoPipe,
|
|
20748
|
+
name: "transloco"
|
|
20749
|
+
}
|
|
20750
|
+
],
|
|
20751
|
+
changeDetection: i0.ChangeDetectionStrategy.OnPush
|
|
20752
|
+
});
|
|
20753
|
+
};
|
|
20754
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
20755
|
+
minVersion: "12.0.0",
|
|
20756
|
+
version: "22.2.1",
|
|
20757
|
+
ngImport: i0,
|
|
20758
|
+
type: KeyboardShortcutsListComponent,
|
|
20759
|
+
decorators: [{
|
|
20760
|
+
type: Component,
|
|
20761
|
+
args: [{
|
|
20762
|
+
selector: "keyboard-shortcuts-list",
|
|
20763
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
20764
|
+
imports: [
|
|
20765
|
+
TranslocoPipe,
|
|
20766
|
+
KbdComponent,
|
|
20767
|
+
KbdGroupComponent
|
|
20768
|
+
],
|
|
20769
|
+
providers: [provideTranslocoScope("dialogs")],
|
|
20770
|
+
template: `
|
|
20771
|
+
@if (groups().length === 0) {
|
|
20772
|
+
<p>{{ "dialogs.keyboardShortcuts.empty" | transloco }}</p>
|
|
20773
|
+
}
|
|
20774
|
+
|
|
20775
|
+
@for (group of groups(); track group.titleKey ?? "") {
|
|
20776
|
+
@if (group.titleKey) {
|
|
20777
|
+
<h3 class="mt-(--spacing-md) mb-(--spacing-4xs) text-(length:--size-lg) font-semibold first:mt-0">{{ group.titleKey | transloco }}</h3>
|
|
20778
|
+
}
|
|
20779
|
+
<dl>
|
|
20780
|
+
@for (shortcut of group.items; track shortcut) {
|
|
20781
|
+
<div class="flex items-center justify-between gap-(--spacing-md) py-(--spacing-4xs)">
|
|
20782
|
+
<dt>{{ shortcut.labelKey! | transloco }}</dt>
|
|
20783
|
+
<dd class="flex items-center gap-(--spacing-3xs)">
|
|
20784
|
+
<!-- One group per combination: a shortcut may answer to several (Alt+0 and
|
|
20785
|
+
Shift+?). The group carries the whole combination as its name and the keys
|
|
20786
|
+
are hidden, otherwise a screen reader reads out every key and separator as
|
|
20787
|
+
a separate item. -->
|
|
20788
|
+
<!-- comboIndex is captured explicitly: inside the nested loop $index would
|
|
20789
|
+
refer to the keys, not to the combination.
|
|
20790
|
+
(No backticks in this comment: the template is a template literal.) -->
|
|
20791
|
+
@for (aria of shortcut.aria; track aria; let first = $first, comboIndex = $index) {
|
|
20792
|
+
@if (!first) {
|
|
20793
|
+
<span aria-hidden="true">{{ "dialogs.keyboardShortcuts.or" | transloco }}</span>
|
|
20794
|
+
}
|
|
20795
|
+
<KbdGroup [attr.aria-label]="aria">
|
|
20796
|
+
@for (part of shortcut.displayParts[comboIndex]; track $index; let firstPart = $first) {
|
|
20797
|
+
@if (!firstPart) {
|
|
20798
|
+
<span aria-hidden="true">+</span>
|
|
20799
|
+
}
|
|
20800
|
+
<kbd aria-hidden="true">{{ part }}</kbd>
|
|
20801
|
+
}
|
|
20802
|
+
</KbdGroup>
|
|
20803
|
+
}
|
|
20804
|
+
</dd>
|
|
20805
|
+
</div>
|
|
20806
|
+
}
|
|
20807
|
+
</dl>
|
|
20808
|
+
}
|
|
20809
|
+
`
|
|
20810
|
+
}]
|
|
20811
|
+
}]
|
|
20812
|
+
});
|
|
20813
|
+
var KeyboardShortcutsDialogComponent = class KeyboardShortcutsDialogComponent {
|
|
20814
|
+
call = injectCallRef();
|
|
20815
|
+
dialog = viewChild(DialogComponent, ...ngDevMode ? [{ debugName: "dialog" }] : /* istanbul ignore next */ []);
|
|
20678
20816
|
constructor() {
|
|
20679
20817
|
afterNextRender(() => this.dialog()?.showModal());
|
|
20680
20818
|
}
|
|
@@ -20687,7 +20825,7 @@ var KeyboardShortcutsDialogComponent = class KeyboardShortcutsDialogComponent {
|
|
|
20687
20825
|
target: i0.ɵɵFactoryTarget.Component
|
|
20688
20826
|
});
|
|
20689
20827
|
static ɵcmp = i0.ɵɵngDeclareComponent({
|
|
20690
|
-
minVersion: "17.
|
|
20828
|
+
minVersion: "17.2.0",
|
|
20691
20829
|
version: "22.2.1",
|
|
20692
20830
|
type: KeyboardShortcutsDialogComponent,
|
|
20693
20831
|
isStandalone: true,
|
|
@@ -20709,44 +20847,7 @@ var KeyboardShortcutsDialogComponent = class KeyboardShortcutsDialogComponent {
|
|
|
20709
20847
|
</DialogHeader>
|
|
20710
20848
|
|
|
20711
20849
|
<DialogBody>
|
|
20712
|
-
|
|
20713
|
-
<p>{{ "dialogs.keyboardShortcuts.empty" | transloco }}</p>
|
|
20714
|
-
}
|
|
20715
|
-
|
|
20716
|
-
@for (group of groups(); track group.titleKey ?? "") {
|
|
20717
|
-
@if (group.titleKey) {
|
|
20718
|
-
<h3 class="mt-(--spacing-md) mb-(--spacing-4xs) font-semibold first:mt-0">{{ group.titleKey | transloco }}</h3>
|
|
20719
|
-
}
|
|
20720
|
-
<dl>
|
|
20721
|
-
@for (shortcut of group.items; track shortcut) {
|
|
20722
|
-
<div class="flex items-center justify-between gap-(--spacing-md) py-(--spacing-4xs)">
|
|
20723
|
-
<dt>{{ shortcut.labelKey! | transloco }}</dt>
|
|
20724
|
-
<dd class="flex items-center gap-(--spacing-3xs)">
|
|
20725
|
-
<!-- One group per combination: a shortcut may answer to several (Alt+0 and
|
|
20726
|
-
Shift+?). The group carries the whole combination as its name and the keys
|
|
20727
|
-
are hidden, otherwise a screen reader reads out every key and separator as
|
|
20728
|
-
a separate item. -->
|
|
20729
|
-
<!-- comboIndex is captured explicitly: inside the nested loop $index would
|
|
20730
|
-
refer to the keys, not to the combination.
|
|
20731
|
-
(No backticks in this comment: the template is a template literal.) -->
|
|
20732
|
-
@for (aria of shortcut.aria; track aria; let first = $first, comboIndex = $index) {
|
|
20733
|
-
@if (!first) {
|
|
20734
|
-
<span aria-hidden="true">{{ "dialogs.keyboardShortcuts.or" | transloco }}</span>
|
|
20735
|
-
}
|
|
20736
|
-
<KbdGroup [attr.aria-label]="aria">
|
|
20737
|
-
@for (part of shortcut.displayParts[comboIndex]; track $index; let firstPart = $first) {
|
|
20738
|
-
@if (!firstPart) {
|
|
20739
|
-
<span aria-hidden="true">+</span>
|
|
20740
|
-
}
|
|
20741
|
-
<kbd aria-hidden="true">{{ part }}</kbd>
|
|
20742
|
-
}
|
|
20743
|
-
</KbdGroup>
|
|
20744
|
-
}
|
|
20745
|
-
</dd>
|
|
20746
|
-
</div>
|
|
20747
|
-
}
|
|
20748
|
-
</dl>
|
|
20749
|
-
}
|
|
20850
|
+
<keyboard-shortcuts-list />
|
|
20750
20851
|
</DialogBody>
|
|
20751
20852
|
|
|
20752
20853
|
<DialogFooter>
|
|
@@ -20812,15 +20913,9 @@ var KeyboardShortcutsDialogComponent = class KeyboardShortcutsDialogComponent {
|
|
|
20812
20913
|
inputs: ["class"]
|
|
20813
20914
|
},
|
|
20814
20915
|
{
|
|
20815
|
-
kind: "
|
|
20816
|
-
type:
|
|
20817
|
-
selector: "
|
|
20818
|
-
inputs: ["class", "size"]
|
|
20819
|
-
},
|
|
20820
|
-
{
|
|
20821
|
-
kind: "directive",
|
|
20822
|
-
type: KbdGroupComponent,
|
|
20823
|
-
selector: "kbd-group, kbdgroup, KbdGroup"
|
|
20916
|
+
kind: "component",
|
|
20917
|
+
type: KeyboardShortcutsListComponent,
|
|
20918
|
+
selector: "keyboard-shortcuts-list"
|
|
20824
20919
|
},
|
|
20825
20920
|
{
|
|
20826
20921
|
kind: "pipe",
|
|
@@ -20850,8 +20945,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
20850
20945
|
DialogTitleComponent,
|
|
20851
20946
|
DialogBodyComponent,
|
|
20852
20947
|
DialogFooterComponent,
|
|
20853
|
-
|
|
20854
|
-
KbdGroupComponent
|
|
20948
|
+
KeyboardShortcutsListComponent
|
|
20855
20949
|
],
|
|
20856
20950
|
providers: [provideTranslocoScope("dialogs")],
|
|
20857
20951
|
template: `
|
|
@@ -20862,44 +20956,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
20862
20956
|
</DialogHeader>
|
|
20863
20957
|
|
|
20864
20958
|
<DialogBody>
|
|
20865
|
-
|
|
20866
|
-
<p>{{ "dialogs.keyboardShortcuts.empty" | transloco }}</p>
|
|
20867
|
-
}
|
|
20868
|
-
|
|
20869
|
-
@for (group of groups(); track group.titleKey ?? "") {
|
|
20870
|
-
@if (group.titleKey) {
|
|
20871
|
-
<h3 class="mt-(--spacing-md) mb-(--spacing-4xs) font-semibold first:mt-0">{{ group.titleKey | transloco }}</h3>
|
|
20872
|
-
}
|
|
20873
|
-
<dl>
|
|
20874
|
-
@for (shortcut of group.items; track shortcut) {
|
|
20875
|
-
<div class="flex items-center justify-between gap-(--spacing-md) py-(--spacing-4xs)">
|
|
20876
|
-
<dt>{{ shortcut.labelKey! | transloco }}</dt>
|
|
20877
|
-
<dd class="flex items-center gap-(--spacing-3xs)">
|
|
20878
|
-
<!-- One group per combination: a shortcut may answer to several (Alt+0 and
|
|
20879
|
-
Shift+?). The group carries the whole combination as its name and the keys
|
|
20880
|
-
are hidden, otherwise a screen reader reads out every key and separator as
|
|
20881
|
-
a separate item. -->
|
|
20882
|
-
<!-- comboIndex is captured explicitly: inside the nested loop $index would
|
|
20883
|
-
refer to the keys, not to the combination.
|
|
20884
|
-
(No backticks in this comment: the template is a template literal.) -->
|
|
20885
|
-
@for (aria of shortcut.aria; track aria; let first = $first, comboIndex = $index) {
|
|
20886
|
-
@if (!first) {
|
|
20887
|
-
<span aria-hidden="true">{{ "dialogs.keyboardShortcuts.or" | transloco }}</span>
|
|
20888
|
-
}
|
|
20889
|
-
<KbdGroup [attr.aria-label]="aria">
|
|
20890
|
-
@for (part of shortcut.displayParts[comboIndex]; track $index; let firstPart = $first) {
|
|
20891
|
-
@if (!firstPart) {
|
|
20892
|
-
<span aria-hidden="true">+</span>
|
|
20893
|
-
}
|
|
20894
|
-
<kbd aria-hidden="true">{{ part }}</kbd>
|
|
20895
|
-
}
|
|
20896
|
-
</KbdGroup>
|
|
20897
|
-
}
|
|
20898
|
-
</dd>
|
|
20899
|
-
</div>
|
|
20900
|
-
}
|
|
20901
|
-
</dl>
|
|
20902
|
-
}
|
|
20959
|
+
<keyboard-shortcuts-list />
|
|
20903
20960
|
</DialogBody>
|
|
20904
20961
|
|
|
20905
20962
|
<DialogFooter>
|
|
@@ -31045,11 +31102,6 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
31045
31102
|
}]
|
|
31046
31103
|
}] }
|
|
31047
31104
|
});
|
|
31048
|
-
const SUPPORTED_LANGUAGES = [
|
|
31049
|
-
"en",
|
|
31050
|
-
"fr",
|
|
31051
|
-
"de"
|
|
31052
|
-
];
|
|
31053
31105
|
const sameKeys = (a, b) => a.length === b.length && a.every((key, index) => key === b[index]);
|
|
31054
31106
|
const userProfileSchema = (editing) => schema((p) => {
|
|
31055
31107
|
applyEach(p.data, (field) => readonly(field, { when: ({ key }) => `data.${key()}` !== editing() }));
|
|
@@ -31057,7 +31109,6 @@ const userProfileSchema = (editing) => schema((p) => {
|
|
|
31057
31109
|
});
|
|
31058
31110
|
var UserProfileFormComponent = class UserProfileFormComponent {
|
|
31059
31111
|
principalStore = inject(PrincipalStore);
|
|
31060
|
-
userSettingsStore = inject(UserSettingsStore);
|
|
31061
31112
|
userProfileService = inject(UserProfileService);
|
|
31062
31113
|
appStore = inject(AppStore);
|
|
31063
31114
|
client = inject(ATOMIC_CLIENT);
|
|
@@ -31105,26 +31156,6 @@ var UserProfileFormComponent = class UserProfileFormComponent {
|
|
|
31105
31156
|
initials = computed(() => this.principalStore.initials(), ...ngDevMode ? [{ debugName: "initials" }] : /* istanbul ignore next */ []);
|
|
31106
31157
|
username = computed(() => this.principalStore.name() || this.principalStore.email() || null, ...ngDevMode ? [{ debugName: "username" }] : /* istanbul ignore next */ []);
|
|
31107
31158
|
propertyToEdit = signal(void 0, ...ngDevMode ? [{ debugName: "propertyToEdit" }] : /* istanbul ignore next */ []);
|
|
31108
|
-
currentLanguage = model(null, ...ngDevMode ? [{ debugName: "currentLanguage" }] : /* istanbul ignore next */ []);
|
|
31109
|
-
allLanguages = [
|
|
31110
|
-
{
|
|
31111
|
-
code: "en",
|
|
31112
|
-
label: "English",
|
|
31113
|
-
icon: FlagEnglishIcon
|
|
31114
|
-
},
|
|
31115
|
-
{
|
|
31116
|
-
code: "fr",
|
|
31117
|
-
label: "Français",
|
|
31118
|
-
icon: FlagFrenchIcon
|
|
31119
|
-
},
|
|
31120
|
-
{
|
|
31121
|
-
code: "de",
|
|
31122
|
-
label: "Deutsch",
|
|
31123
|
-
icon: FlagGermanIcon
|
|
31124
|
-
}
|
|
31125
|
-
];
|
|
31126
|
-
languageCodes = [...SUPPORTED_LANGUAGES];
|
|
31127
|
-
languageLabel = (code) => this.allLanguages.find((lang) => lang.code === code)?.label ?? code;
|
|
31128
31159
|
changingPassword = signal(false, ...ngDevMode ? [{ debugName: "changingPassword" }] : /* istanbul ignore next */ []);
|
|
31129
31160
|
allowChangePassword = computed(() => {
|
|
31130
31161
|
const useCredentials = this.client.config.authMode?.kind === "credentials";
|
|
@@ -31155,7 +31186,6 @@ var UserProfileFormComponent = class UserProfileFormComponent {
|
|
|
31155
31186
|
submission: { action: async () => this.saveEditedProperty() }
|
|
31156
31187
|
});
|
|
31157
31188
|
constructor() {
|
|
31158
|
-
this.currentLanguage.set(this.transloco.getActiveLang());
|
|
31159
31189
|
effect(() => {
|
|
31160
31190
|
if (this.userProfileResource.error()?.status === 404) this.createUserProfile();
|
|
31161
31191
|
});
|
|
@@ -31293,17 +31323,6 @@ var UserProfileFormComponent = class UserProfileFormComponent {
|
|
|
31293
31323
|
reader.readAsDataURL(element.files[0]);
|
|
31294
31324
|
reader.onload = () => void this.patchProperty("data", "profilePhoto", reader.result);
|
|
31295
31325
|
}
|
|
31296
|
-
onLanguageChange(code) {
|
|
31297
|
-
if (!code) return;
|
|
31298
|
-
this.currentLanguage.set(code);
|
|
31299
|
-
this.changeLanguage();
|
|
31300
|
-
}
|
|
31301
|
-
changeLanguage() {
|
|
31302
|
-
const language = this.currentLanguage();
|
|
31303
|
-
if (!language) return;
|
|
31304
|
-
this.userSettingsStore.updateLanguage(language);
|
|
31305
|
-
if (this.transloco.getActiveLang() !== language) this.transloco.setActiveLang(language);
|
|
31306
|
-
}
|
|
31307
31326
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
31308
31327
|
minVersion: "12.0.0",
|
|
31309
31328
|
version: "22.2.1",
|
|
@@ -31318,18 +31337,10 @@ var UserProfileFormComponent = class UserProfileFormComponent {
|
|
|
31318
31337
|
type: UserProfileFormComponent,
|
|
31319
31338
|
isStandalone: true,
|
|
31320
31339
|
selector: "user-profile-form, UserProfileForm, userprofileform",
|
|
31321
|
-
inputs: { currentLanguage: {
|
|
31322
|
-
classPropertyName: "currentLanguage",
|
|
31323
|
-
publicName: "currentLanguage",
|
|
31324
|
-
isSignal: true,
|
|
31325
|
-
isRequired: false,
|
|
31326
|
-
transformFunction: null
|
|
31327
|
-
} },
|
|
31328
|
-
outputs: { currentLanguage: "currentLanguageChange" },
|
|
31329
31340
|
host: { classAttribute: "flex flex-col h-full overflow-hidden" },
|
|
31330
31341
|
providers: [provideTranslocoScope("user-profile", "login")],
|
|
31331
31342
|
ngImport: i0,
|
|
31332
|
-
template: "@if (userProfileResource.hasValue()) {\n @let profile = user();\n <div class=\"flex min-h-0 flex-1 flex-col gap-2 p-4\">\n <div class=\"mb-4 flex\">\n @if (allowProfilePhoto()) {\n @let profilePhoto = profile?.data?.profilePhoto;\n <!-- AVATAR — a real <button>: the avatar opens the file picker, so it has to be reachable and\n activatable with the keyboard, which a click handler on a <div> never is. -->\n <button\n type=\"button\"\n variant=\"none\"\n class=\"cursor-pointer rounded-full\"\n [title]=\"'userProfile.changePhoto' | transloco\"\n [attr.aria-label]=\"'userProfile.changePhoto' | transloco\"\n (click)=\"avatarInput.click()\">\n <Avatar fill=\"secondary\" class=\"size-14\">\n @if (profilePhoto) {\n <!-- alt=\"\": the button already carries the accessible name, the image would repeat it. -->\n <AvatarImage [src]=\"profilePhoto\" width=\"44\" height=\"44\" alt=\"\" />\n }\n <AvatarFallback class=\"size-full text-lg\">\n @if (initials()) {\n <span>{{ initials() }}</span>\n } @else {\n <UserIcon class=\"size-7\" />\n }\n </AvatarFallback>\n </Avatar>\n </button>\n @if (profilePhoto) {\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n class=\"text-destructive cursor-pointer\"\n [title]=\"'userProfile.deleteProperty' | transloco\"\n [attr.aria-label]=\"\n 'userProfile.deletePropertyNamed'\n | transloco\n : { property: 'userProfile.data.profilePhoto' | transloco }\n \"\n (click)=\"onDeleteData('data', 'profilePhoto')\">\n <TrashIcon />\n </button>\n }\n }\n <div class=\"grow\"></div>\n <div class=\"flex flex-col gap-4\">\n <!-- LANGUAGE — outside the <form> below: it is a user setting applied on the spot, not a\n profile property submitted with the rest. The trigger is projected rather than left to the\n default one so it can carry an id, which is what makes the <label for> name the combobox —\n SelectMenu's own `fieldLabel` renders a <span>, which names nothing.\n The label goes in the `.select-label` slot: the DS wrap paints it (same size and weight as\n the property labels below) and provides the label-to-field gap, so this needs no wrapper. -->\n <SelectMenu\n class=\"w-fit\"\n size=\"md\"\n [options]=\"languageCodes\"\n [label]=\"languageLabel\"\n [value]=\"currentLanguage()\"\n (valueChange)=\"onLanguageChange($event)\">\n <label class=\"select-label\" for=\"user-profile-language\">{{\n \"userProfile.language\" | transloco\n }}</label>\n <button\n selectTrigger\n type=\"button\"\n id=\"user-profile-language\"\n class=\"select-trigger\">\n <span class=\"select-trigger-label\">{{\n languageLabel(currentLanguage() ?? \"\")\n }}</span>\n <chevron-down-icon class=\"select-chevron\" />\n </button>\n </SelectMenu>\n <!-- CHANGE PASSWORD -->\n @if (allowChangePassword()) {\n <button\n type=\"button\"\n [disabled]=\"changingPassword()\"\n (click)=\"changingPassword.set(true)\">\n {{ \"login.changePassword\" | transloco }}\n </button>\n }\n </div>\n </div>\n @if (changingPassword()) {\n <!-- ChangePassword renders its own <form [formRoot]>: it stays in this branch, mutually\n exclusive with the profile form below, so the two never nest. -->\n <ChangePassword\n [username]=\"username()\"\n [redirectAfterSuccess]=\"false\"\n [redirectAfterCancel]=\"false\"\n (cancel)=\"changingPassword.set(false)\"\n (success)=\"changingPassword.set(false)\" />\n } @else {\n <!-- p: the focus outline of the fields is drawn *outside* their box, and this is the\n scrolling ancestor — without a gutter it clips the ring on every edge it touches\n (left for all of them, bottom for the last one, top for the first). -->\n <div class=\"min-h-0 flex-1 overflow-auto p-(--spacing-3xs)\">\n <!--\n [formRoot] pose novalidate, intercepte submit et appelle preventDefault() avant de déclencher\n submit() sur le field tree. Un seul bouton type=\"submit\" est rendu à la fois — celui de la\n ligne en cours d'édition — et l'action de soumission persiste cette seule propriété.\n -->\n <!-- aria-label: a <form> with no accessible name is not exposed as a form landmark, so the\n fields could not be reached as a whole. aria-busy holds the announcement back while a\n property is being persisted. -->\n <form\n [formRoot]=\"profileForm\"\n [attr.aria-label]=\"'userProfile.profileFields' | transloco\"\n [attr.aria-busy]=\"profileForm().submitting() ? 'true' : null\">\n @for (\n property of properties();\n track property.category + \".\" + property.key\n ) {\n @let category = property.category;\n @let key = property.key;\n @let field = profileForm[category][key];\n @let label =\n property.translated\n ? (\"userProfile.data.\" + key | transloco)\n : key;\n @let editing = isEditing(category, key);\n @let invalid = showError(field);\n <!-- role=\"group\" named by the label: without it the four action buttons of a row are\n announced out of context, as a flat run of \"Edit …\" / \"Delete …\" with nothing tying\n them to the property they belong to. -->\n <div\n role=\"group\"\n [attr.aria-labelledby]=\"labelId(category, key)\"\n class=\"flex flex-col gap-(--spacing-3xs)\">\n <label\n [id]=\"labelId(category, key)\"\n [for]=\"fieldId(category, key)\"\n [class]=\"labelClass\"\n >{{ label }}</label\n >\n <div class=\"group flex flex-row\">\n <div class=\"min-w-0 grow\">\n <textarea-group\n resize=\"auto\"\n [maxRows]=\"3\"\n [scheme]=\"invalid ? 'error' : 'default'\">\n <!-- No [attr.readonly] here: [formField] writes the readonly attribute itself from\n the field state, which the schema's readonly() rule drives. The native\n readonly attribute is what exposes aria-readonly — do not duplicate it. -->\n <textarea\n textarea-control\n rows=\"1\"\n [id]=\"fieldId(category, key)\"\n [formField]=\"field\"\n [attr.placeholder]=\"'userProfile.notDefined' | transloco\"\n [attr.aria-invalid]=\"invalid ? 'true' : null\"\n [attr.aria-describedby]=\"\n invalid ? errorId(category, key) : null\n \"\n [attr.aria-keyshortcuts]=\"editing ? 'Escape' : null\"\n (focus)=\"onFieldFocus($event)\"\n (keydown.escape)=\"onCancelEdit(category, key)\"></textarea>\n </textarea-group>\n </div>\n <!-- group-focus-within, and not only group-hover: `invisible` takes the buttons out of\n the tab order, so without it editing or deleting a property is unreachable with a\n keyboard. Focusing the field of the row reveals its actions. -->\n @if (editing) {\n <button\n type=\"submit\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n class=\"visible mx-2 cursor-pointer sm:invisible sm:group-focus-within:visible sm:group-hover:visible\"\n [disabled]=\"profileForm().submitting()\"\n [title]=\"'userProfile.saveProperty' | transloco\"\n [attr.aria-label]=\"\n 'userProfile.savePropertyNamed'\n | transloco: { property: label }\n \">\n <save-icon />\n </button>\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n class=\"visible cursor-pointer sm:invisible sm:group-focus-within:visible sm:group-hover:visible\"\n [title]=\"'userProfile.cancelEdit' | transloco\"\n [attr.aria-label]=\"\n 'userProfile.cancelEditNamed'\n | transloco: { property: label }\n \"\n (click)=\"onCancelEdit(category, key)\">\n <UndoIcon />\n </button>\n } @else {\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n [id]=\"editButtonId(category, key)\"\n class=\"visible mx-2 cursor-pointer sm:invisible sm:group-focus-within:visible sm:group-hover:visible\"\n [title]=\"'userProfile.editProperty' | transloco\"\n [attr.aria-label]=\"\n 'userProfile.editPropertyNamed'\n | transloco: { property: label }\n \"\n (click)=\"onEdit(category, key)\">\n <EditIcon />\n </button>\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n class=\"text-destructive visible cursor-pointer sm:invisible sm:group-focus-within:visible sm:group-hover:visible\"\n [title]=\"'userProfile.deleteProperty' | transloco\"\n [attr.aria-label]=\"\n 'userProfile.deletePropertyNamed'\n | transloco: { property: label }\n \"\n (click)=\"onDeleteData(category, key)\">\n <TrashIcon />\n </button>\n }\n </div>\n @if (invalid) {\n <p\n [id]=\"errorId(category, key)\"\n role=\"alert\"\n [class]=\"errorClass\">\n {{ errorKey(field) | transloco }}\n </p>\n }\n </div>\n }\n </form>\n <!-- Always in the DOM, and only its text changes: a live region inserted at the same time as\n its content is not reliably announced. `sr-only` — the transitions it describes are\n already visible on screen. -->\n <p class=\"sr-only\" role=\"status\" aria-live=\"polite\">\n {{ statusMessage() }}\n </p>\n </div>\n }\n </div>\n} @else if (userProfileResource.isLoading()) {\n <p role=\"status\">{{ \"userProfile.loading\" | transloco }}</p>\n} @else if (userProfileResource.error()) {\n <p role=\"alert\">{{ \"userProfile.loadError\" | transloco }}</p>\n}\n\n<!-- AVATAR UPLOAD — `hidden` keeps it out of both the tab order and the accessibility tree, which is\n intended: it is opened by the avatar button above. It also sits outside the <form>. -->\n<input\n #avatarInput\n class=\"hidden\"\n type=\"file\"\n accept=\"image/*\"\n (change)=\"uploadAvatar($event)\" />\n",
|
|
31343
|
+
template: "@if (userProfileResource.hasValue()) {\n @let profile = user();\n <div class=\"flex min-h-0 flex-1 flex-col gap-2 p-4\">\n <div class=\"mb-4 flex\">\n @if (allowProfilePhoto()) {\n @let profilePhoto = profile?.data?.profilePhoto;\n <!-- AVATAR — a real <button>: the avatar opens the file picker, so it has to be reachable and\n activatable with the keyboard, which a click handler on a <div> never is. -->\n <button\n type=\"button\"\n variant=\"none\"\n class=\"cursor-pointer rounded-full\"\n [title]=\"'userProfile.changePhoto' | transloco\"\n [attr.aria-label]=\"'userProfile.changePhoto' | transloco\"\n (click)=\"avatarInput.click()\">\n <Avatar fill=\"secondary\" class=\"size-14\">\n @if (profilePhoto) {\n <!-- alt=\"\": the button already carries the accessible name, the image would repeat it. -->\n <AvatarImage [src]=\"profilePhoto\" width=\"44\" height=\"44\" alt=\"\" />\n }\n <AvatarFallback class=\"size-full text-lg\">\n @if (initials()) {\n <span>{{ initials() }}</span>\n } @else {\n <UserIcon class=\"size-7\" />\n }\n </AvatarFallback>\n </Avatar>\n </button>\n @if (profilePhoto) {\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n class=\"text-destructive cursor-pointer\"\n [title]=\"'userProfile.deleteProperty' | transloco\"\n [attr.aria-label]=\"\n 'userProfile.deletePropertyNamed'\n | transloco\n : { property: 'userProfile.data.profilePhoto' | transloco }\n \"\n (click)=\"onDeleteData('data', 'profilePhoto')\">\n <TrashIcon />\n </button>\n }\n }\n <div class=\"grow\"></div>\n <div class=\"flex flex-col gap-4\">\n <!-- CHANGE PASSWORD -->\n @if (allowChangePassword()) {\n <button\n type=\"button\"\n [disabled]=\"changingPassword()\"\n (click)=\"changingPassword.set(true)\">\n {{ \"login.changePassword\" | transloco }}\n </button>\n }\n </div>\n </div>\n @if (changingPassword()) {\n <!-- ChangePassword renders its own <form [formRoot]>: it stays in this branch, mutually\n exclusive with the profile form below, so the two never nest. -->\n <ChangePassword\n [username]=\"username()\"\n [redirectAfterSuccess]=\"false\"\n [redirectAfterCancel]=\"false\"\n (cancel)=\"changingPassword.set(false)\"\n (success)=\"changingPassword.set(false)\" />\n } @else {\n <!-- p: the focus outline of the fields is drawn *outside* their box, and this is the\n scrolling ancestor — without a gutter it clips the ring on every edge it touches\n (left for all of them, bottom for the last one, top for the first). -->\n <div class=\"min-h-0 flex-1 overflow-auto p-(--spacing-3xs)\">\n <!--\n [formRoot] pose novalidate, intercepte submit et appelle preventDefault() avant de déclencher\n submit() sur le field tree. Un seul bouton type=\"submit\" est rendu à la fois — celui de la\n ligne en cours d'édition — et l'action de soumission persiste cette seule propriété.\n -->\n <!-- aria-label: a <form> with no accessible name is not exposed as a form landmark, so the\n fields could not be reached as a whole. aria-busy holds the announcement back while a\n property is being persisted. -->\n <form\n [formRoot]=\"profileForm\"\n [attr.aria-label]=\"'userProfile.profileFields' | transloco\"\n [attr.aria-busy]=\"profileForm().submitting() ? 'true' : null\">\n @for (\n property of properties();\n track property.category + \".\" + property.key\n ) {\n @let category = property.category;\n @let key = property.key;\n @let field = profileForm[category][key];\n @let label =\n property.translated\n ? (\"userProfile.data.\" + key | transloco)\n : key;\n @let editing = isEditing(category, key);\n @let invalid = showError(field);\n <!-- role=\"group\" named by the label: without it the four action buttons of a row are\n announced out of context, as a flat run of \"Edit …\" / \"Delete …\" with nothing tying\n them to the property they belong to. -->\n <div\n role=\"group\"\n [attr.aria-labelledby]=\"labelId(category, key)\"\n class=\"flex flex-col gap-(--spacing-3xs)\">\n <label\n [id]=\"labelId(category, key)\"\n [for]=\"fieldId(category, key)\"\n [class]=\"labelClass\"\n >{{ label }}</label\n >\n <div class=\"group flex flex-row\">\n <div class=\"min-w-0 grow\">\n <textarea-group\n resize=\"auto\"\n [maxRows]=\"3\"\n [scheme]=\"invalid ? 'error' : 'default'\">\n <!-- No [attr.readonly] here: [formField] writes the readonly attribute itself from\n the field state, which the schema's readonly() rule drives. The native\n readonly attribute is what exposes aria-readonly — do not duplicate it. -->\n <textarea\n textarea-control\n rows=\"1\"\n [id]=\"fieldId(category, key)\"\n [formField]=\"field\"\n [attr.placeholder]=\"'userProfile.notDefined' | transloco\"\n [attr.aria-invalid]=\"invalid ? 'true' : null\"\n [attr.aria-describedby]=\"\n invalid ? errorId(category, key) : null\n \"\n [attr.aria-keyshortcuts]=\"editing ? 'Escape' : null\"\n (focus)=\"onFieldFocus($event)\"\n (keydown.escape)=\"onCancelEdit(category, key)\"></textarea>\n </textarea-group>\n </div>\n <!-- group-focus-within, and not only group-hover: `invisible` takes the buttons out of\n the tab order, so without it editing or deleting a property is unreachable with a\n keyboard. Focusing the field of the row reveals its actions. -->\n @if (editing) {\n <button\n type=\"submit\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n class=\"visible mx-2 cursor-pointer sm:invisible sm:group-focus-within:visible sm:group-hover:visible\"\n [disabled]=\"profileForm().submitting()\"\n [title]=\"'userProfile.saveProperty' | transloco\"\n [attr.aria-label]=\"\n 'userProfile.savePropertyNamed'\n | transloco: { property: label }\n \">\n <save-icon />\n </button>\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n class=\"visible cursor-pointer sm:invisible sm:group-focus-within:visible sm:group-hover:visible\"\n [title]=\"'userProfile.cancelEdit' | transloco\"\n [attr.aria-label]=\"\n 'userProfile.cancelEditNamed'\n | transloco: { property: label }\n \"\n (click)=\"onCancelEdit(category, key)\">\n <UndoIcon />\n </button>\n } @else {\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n [id]=\"editButtonId(category, key)\"\n class=\"visible mx-2 cursor-pointer sm:invisible sm:group-focus-within:visible sm:group-hover:visible\"\n [title]=\"'userProfile.editProperty' | transloco\"\n [attr.aria-label]=\"\n 'userProfile.editPropertyNamed'\n | transloco: { property: label }\n \"\n (click)=\"onEdit(category, key)\">\n <EditIcon />\n </button>\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n class=\"text-destructive visible cursor-pointer sm:invisible sm:group-focus-within:visible sm:group-hover:visible\"\n [title]=\"'userProfile.deleteProperty' | transloco\"\n [attr.aria-label]=\"\n 'userProfile.deletePropertyNamed'\n | transloco: { property: label }\n \"\n (click)=\"onDeleteData(category, key)\">\n <TrashIcon />\n </button>\n }\n </div>\n @if (invalid) {\n <p\n [id]=\"errorId(category, key)\"\n role=\"alert\"\n [class]=\"errorClass\">\n {{ errorKey(field) | transloco }}\n </p>\n }\n </div>\n }\n </form>\n <!-- Always in the DOM, and only its text changes: a live region inserted at the same time as\n its content is not reliably announced. `sr-only` — the transitions it describes are\n already visible on screen. -->\n <p class=\"sr-only\" role=\"status\" aria-live=\"polite\">\n {{ statusMessage() }}\n </p>\n </div>\n }\n </div>\n} @else if (userProfileResource.isLoading()) {\n <p role=\"status\">{{ \"userProfile.loading\" | transloco }}</p>\n} @else if (userProfileResource.error()) {\n <p role=\"alert\">{{ \"userProfile.loadError\" | transloco }}</p>\n}\n\n<!-- AVATAR UPLOAD — `hidden` keeps it out of both the tab order and the accessibility tree, which is\n intended: it is opened by the avatar button above. It also sits outside the <form>. -->\n<input\n #avatarInput\n class=\"hidden\"\n type=\"file\"\n accept=\"image/*\"\n (change)=\"uploadAvatar($event)\" />\n",
|
|
31333
31344
|
dependencies: [
|
|
31334
31345
|
{
|
|
31335
31346
|
kind: "directive",
|
|
@@ -31422,45 +31433,6 @@ var UserProfileFormComponent = class UserProfileFormComponent {
|
|
|
31422
31433
|
"size"
|
|
31423
31434
|
]
|
|
31424
31435
|
},
|
|
31425
|
-
{
|
|
31426
|
-
kind: "component",
|
|
31427
|
-
type: SelectComponent,
|
|
31428
|
-
selector: "SelectMenu,select-menu",
|
|
31429
|
-
inputs: [
|
|
31430
|
-
"value",
|
|
31431
|
-
"options",
|
|
31432
|
-
"placement",
|
|
31433
|
-
"size",
|
|
31434
|
-
"fieldLabel",
|
|
31435
|
-
"aria-label",
|
|
31436
|
-
"placeholder",
|
|
31437
|
-
"hint",
|
|
31438
|
-
"disabled",
|
|
31439
|
-
"class",
|
|
31440
|
-
"selectionMode",
|
|
31441
|
-
"multiple",
|
|
31442
|
-
"wrap",
|
|
31443
|
-
"key",
|
|
31444
|
-
"label",
|
|
31445
|
-
"optionDisabled",
|
|
31446
|
-
"noOptionsText",
|
|
31447
|
-
"contentClass"
|
|
31448
|
-
],
|
|
31449
|
-
outputs: ["valueChange", "touch"],
|
|
31450
|
-
exportAs: ["select"]
|
|
31451
|
-
},
|
|
31452
|
-
{
|
|
31453
|
-
kind: "directive",
|
|
31454
|
-
type: SelectTriggerDirective,
|
|
31455
|
-
selector: "[selectTrigger]",
|
|
31456
|
-
exportAs: ["selectTrigger"]
|
|
31457
|
-
},
|
|
31458
|
-
{
|
|
31459
|
-
kind: "component",
|
|
31460
|
-
type: ChevronDownIcon,
|
|
31461
|
-
selector: "chevron-down, chevron-down-icon, ChevronDown, ChevronDownIcon",
|
|
31462
|
-
inputs: ["class"]
|
|
31463
|
-
},
|
|
31464
31436
|
{
|
|
31465
31437
|
kind: "directive",
|
|
31466
31438
|
type: TextareaComponent,
|
|
@@ -31530,29 +31502,15 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
31530
31502
|
UserIcon,
|
|
31531
31503
|
AvatarImageComponent,
|
|
31532
31504
|
ButtonComponent,
|
|
31533
|
-
SelectComponent,
|
|
31534
|
-
SelectTriggerDirective,
|
|
31535
|
-
ChevronDownIcon,
|
|
31536
31505
|
TextareaComponent,
|
|
31537
31506
|
TextareaControlDirective,
|
|
31538
31507
|
ChangePasswordComponent
|
|
31539
31508
|
],
|
|
31540
31509
|
providers: [provideTranslocoScope("user-profile", "login")],
|
|
31541
|
-
template: "@if (userProfileResource.hasValue()) {\n @let profile = user();\n <div class=\"flex min-h-0 flex-1 flex-col gap-2 p-4\">\n <div class=\"mb-4 flex\">\n @if (allowProfilePhoto()) {\n @let profilePhoto = profile?.data?.profilePhoto;\n <!-- AVATAR — a real <button>: the avatar opens the file picker, so it has to be reachable and\n activatable with the keyboard, which a click handler on a <div> never is. -->\n <button\n type=\"button\"\n variant=\"none\"\n class=\"cursor-pointer rounded-full\"\n [title]=\"'userProfile.changePhoto' | transloco\"\n [attr.aria-label]=\"'userProfile.changePhoto' | transloco\"\n (click)=\"avatarInput.click()\">\n <Avatar fill=\"secondary\" class=\"size-14\">\n @if (profilePhoto) {\n <!-- alt=\"\": the button already carries the accessible name, the image would repeat it. -->\n <AvatarImage [src]=\"profilePhoto\" width=\"44\" height=\"44\" alt=\"\" />\n }\n <AvatarFallback class=\"size-full text-lg\">\n @if (initials()) {\n <span>{{ initials() }}</span>\n } @else {\n <UserIcon class=\"size-7\" />\n }\n </AvatarFallback>\n </Avatar>\n </button>\n @if (profilePhoto) {\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n class=\"text-destructive cursor-pointer\"\n [title]=\"'userProfile.deleteProperty' | transloco\"\n [attr.aria-label]=\"\n 'userProfile.deletePropertyNamed'\n | transloco\n : { property: 'userProfile.data.profilePhoto' | transloco }\n \"\n (click)=\"onDeleteData('data', 'profilePhoto')\">\n <TrashIcon />\n </button>\n }\n }\n <div class=\"grow\"></div>\n <div class=\"flex flex-col gap-4\">\n <!-- LANGUAGE — outside the <form> below: it is a user setting applied on the spot, not a\n profile property submitted with the rest. The trigger is projected rather than left to the\n default one so it can carry an id, which is what makes the <label for> name the combobox —\n SelectMenu's own `fieldLabel` renders a <span>, which names nothing.\n The label goes in the `.select-label` slot: the DS wrap paints it (same size and weight as\n the property labels below) and provides the label-to-field gap, so this needs no wrapper. -->\n <SelectMenu\n class=\"w-fit\"\n size=\"md\"\n [options]=\"languageCodes\"\n [label]=\"languageLabel\"\n [value]=\"currentLanguage()\"\n (valueChange)=\"onLanguageChange($event)\">\n <label class=\"select-label\" for=\"user-profile-language\">{{\n \"userProfile.language\" | transloco\n }}</label>\n <button\n selectTrigger\n type=\"button\"\n id=\"user-profile-language\"\n class=\"select-trigger\">\n <span class=\"select-trigger-label\">{{\n languageLabel(currentLanguage() ?? \"\")\n }}</span>\n <chevron-down-icon class=\"select-chevron\" />\n </button>\n </SelectMenu>\n <!-- CHANGE PASSWORD -->\n @if (allowChangePassword()) {\n <button\n type=\"button\"\n [disabled]=\"changingPassword()\"\n (click)=\"changingPassword.set(true)\">\n {{ \"login.changePassword\" | transloco }}\n </button>\n }\n </div>\n </div>\n @if (changingPassword()) {\n <!-- ChangePassword renders its own <form [formRoot]>: it stays in this branch, mutually\n exclusive with the profile form below, so the two never nest. -->\n <ChangePassword\n [username]=\"username()\"\n [redirectAfterSuccess]=\"false\"\n [redirectAfterCancel]=\"false\"\n (cancel)=\"changingPassword.set(false)\"\n (success)=\"changingPassword.set(false)\" />\n } @else {\n <!-- p: the focus outline of the fields is drawn *outside* their box, and this is the\n scrolling ancestor — without a gutter it clips the ring on every edge it touches\n (left for all of them, bottom for the last one, top for the first). -->\n <div class=\"min-h-0 flex-1 overflow-auto p-(--spacing-3xs)\">\n <!--\n [formRoot] pose novalidate, intercepte submit et appelle preventDefault() avant de déclencher\n submit() sur le field tree. Un seul bouton type=\"submit\" est rendu à la fois — celui de la\n ligne en cours d'édition — et l'action de soumission persiste cette seule propriété.\n -->\n <!-- aria-label: a <form> with no accessible name is not exposed as a form landmark, so the\n fields could not be reached as a whole. aria-busy holds the announcement back while a\n property is being persisted. -->\n <form\n [formRoot]=\"profileForm\"\n [attr.aria-label]=\"'userProfile.profileFields' | transloco\"\n [attr.aria-busy]=\"profileForm().submitting() ? 'true' : null\">\n @for (\n property of properties();\n track property.category + \".\" + property.key\n ) {\n @let category = property.category;\n @let key = property.key;\n @let field = profileForm[category][key];\n @let label =\n property.translated\n ? (\"userProfile.data.\" + key | transloco)\n : key;\n @let editing = isEditing(category, key);\n @let invalid = showError(field);\n <!-- role=\"group\" named by the label: without it the four action buttons of a row are\n announced out of context, as a flat run of \"Edit …\" / \"Delete …\" with nothing tying\n them to the property they belong to. -->\n <div\n role=\"group\"\n [attr.aria-labelledby]=\"labelId(category, key)\"\n class=\"flex flex-col gap-(--spacing-3xs)\">\n <label\n [id]=\"labelId(category, key)\"\n [for]=\"fieldId(category, key)\"\n [class]=\"labelClass\"\n >{{ label }}</label\n >\n <div class=\"group flex flex-row\">\n <div class=\"min-w-0 grow\">\n <textarea-group\n resize=\"auto\"\n [maxRows]=\"3\"\n [scheme]=\"invalid ? 'error' : 'default'\">\n <!-- No [attr.readonly] here: [formField] writes the readonly attribute itself from\n the field state, which the schema's readonly() rule drives. The native\n readonly attribute is what exposes aria-readonly — do not duplicate it. -->\n <textarea\n textarea-control\n rows=\"1\"\n [id]=\"fieldId(category, key)\"\n [formField]=\"field\"\n [attr.placeholder]=\"'userProfile.notDefined' | transloco\"\n [attr.aria-invalid]=\"invalid ? 'true' : null\"\n [attr.aria-describedby]=\"\n invalid ? errorId(category, key) : null\n \"\n [attr.aria-keyshortcuts]=\"editing ? 'Escape' : null\"\n (focus)=\"onFieldFocus($event)\"\n (keydown.escape)=\"onCancelEdit(category, key)\"></textarea>\n </textarea-group>\n </div>\n <!-- group-focus-within, and not only group-hover: `invisible` takes the buttons out of\n the tab order, so without it editing or deleting a property is unreachable with a\n keyboard. Focusing the field of the row reveals its actions. -->\n @if (editing) {\n <button\n type=\"submit\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n class=\"visible mx-2 cursor-pointer sm:invisible sm:group-focus-within:visible sm:group-hover:visible\"\n [disabled]=\"profileForm().submitting()\"\n [title]=\"'userProfile.saveProperty' | transloco\"\n [attr.aria-label]=\"\n 'userProfile.savePropertyNamed'\n | transloco: { property: label }\n \">\n <save-icon />\n </button>\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n class=\"visible cursor-pointer sm:invisible sm:group-focus-within:visible sm:group-hover:visible\"\n [title]=\"'userProfile.cancelEdit' | transloco\"\n [attr.aria-label]=\"\n 'userProfile.cancelEditNamed'\n | transloco: { property: label }\n \"\n (click)=\"onCancelEdit(category, key)\">\n <UndoIcon />\n </button>\n } @else {\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n [id]=\"editButtonId(category, key)\"\n class=\"visible mx-2 cursor-pointer sm:invisible sm:group-focus-within:visible sm:group-hover:visible\"\n [title]=\"'userProfile.editProperty' | transloco\"\n [attr.aria-label]=\"\n 'userProfile.editPropertyNamed'\n | transloco: { property: label }\n \"\n (click)=\"onEdit(category, key)\">\n <EditIcon />\n </button>\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n class=\"text-destructive visible cursor-pointer sm:invisible sm:group-focus-within:visible sm:group-hover:visible\"\n [title]=\"'userProfile.deleteProperty' | transloco\"\n [attr.aria-label]=\"\n 'userProfile.deletePropertyNamed'\n | transloco: { property: label }\n \"\n (click)=\"onDeleteData(category, key)\">\n <TrashIcon />\n </button>\n }\n </div>\n @if (invalid) {\n <p\n [id]=\"errorId(category, key)\"\n role=\"alert\"\n [class]=\"errorClass\">\n {{ errorKey(field) | transloco }}\n </p>\n }\n </div>\n }\n </form>\n <!-- Always in the DOM, and only its text changes: a live region inserted at the same time as\n its content is not reliably announced. `sr-only` — the transitions it describes are\n already visible on screen. -->\n <p class=\"sr-only\" role=\"status\" aria-live=\"polite\">\n {{ statusMessage() }}\n </p>\n </div>\n }\n </div>\n} @else if (userProfileResource.isLoading()) {\n <p role=\"status\">{{ \"userProfile.loading\" | transloco }}</p>\n} @else if (userProfileResource.error()) {\n <p role=\"alert\">{{ \"userProfile.loadError\" | transloco }}</p>\n}\n\n<!-- AVATAR UPLOAD — `hidden` keeps it out of both the tab order and the accessibility tree, which is\n intended: it is opened by the avatar button above. It also sits outside the <form>. -->\n<input\n #avatarInput\n class=\"hidden\"\n type=\"file\"\n accept=\"image/*\"\n (change)=\"uploadAvatar($event)\" />\n"
|
|
31510
|
+
template: "@if (userProfileResource.hasValue()) {\n @let profile = user();\n <div class=\"flex min-h-0 flex-1 flex-col gap-2 p-4\">\n <div class=\"mb-4 flex\">\n @if (allowProfilePhoto()) {\n @let profilePhoto = profile?.data?.profilePhoto;\n <!-- AVATAR — a real <button>: the avatar opens the file picker, so it has to be reachable and\n activatable with the keyboard, which a click handler on a <div> never is. -->\n <button\n type=\"button\"\n variant=\"none\"\n class=\"cursor-pointer rounded-full\"\n [title]=\"'userProfile.changePhoto' | transloco\"\n [attr.aria-label]=\"'userProfile.changePhoto' | transloco\"\n (click)=\"avatarInput.click()\">\n <Avatar fill=\"secondary\" class=\"size-14\">\n @if (profilePhoto) {\n <!-- alt=\"\": the button already carries the accessible name, the image would repeat it. -->\n <AvatarImage [src]=\"profilePhoto\" width=\"44\" height=\"44\" alt=\"\" />\n }\n <AvatarFallback class=\"size-full text-lg\">\n @if (initials()) {\n <span>{{ initials() }}</span>\n } @else {\n <UserIcon class=\"size-7\" />\n }\n </AvatarFallback>\n </Avatar>\n </button>\n @if (profilePhoto) {\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n class=\"text-destructive cursor-pointer\"\n [title]=\"'userProfile.deleteProperty' | transloco\"\n [attr.aria-label]=\"\n 'userProfile.deletePropertyNamed'\n | transloco\n : { property: 'userProfile.data.profilePhoto' | transloco }\n \"\n (click)=\"onDeleteData('data', 'profilePhoto')\">\n <TrashIcon />\n </button>\n }\n }\n <div class=\"grow\"></div>\n <div class=\"flex flex-col gap-4\">\n <!-- CHANGE PASSWORD -->\n @if (allowChangePassword()) {\n <button\n type=\"button\"\n [disabled]=\"changingPassword()\"\n (click)=\"changingPassword.set(true)\">\n {{ \"login.changePassword\" | transloco }}\n </button>\n }\n </div>\n </div>\n @if (changingPassword()) {\n <!-- ChangePassword renders its own <form [formRoot]>: it stays in this branch, mutually\n exclusive with the profile form below, so the two never nest. -->\n <ChangePassword\n [username]=\"username()\"\n [redirectAfterSuccess]=\"false\"\n [redirectAfterCancel]=\"false\"\n (cancel)=\"changingPassword.set(false)\"\n (success)=\"changingPassword.set(false)\" />\n } @else {\n <!-- p: the focus outline of the fields is drawn *outside* their box, and this is the\n scrolling ancestor — without a gutter it clips the ring on every edge it touches\n (left for all of them, bottom for the last one, top for the first). -->\n <div class=\"min-h-0 flex-1 overflow-auto p-(--spacing-3xs)\">\n <!--\n [formRoot] pose novalidate, intercepte submit et appelle preventDefault() avant de déclencher\n submit() sur le field tree. Un seul bouton type=\"submit\" est rendu à la fois — celui de la\n ligne en cours d'édition — et l'action de soumission persiste cette seule propriété.\n -->\n <!-- aria-label: a <form> with no accessible name is not exposed as a form landmark, so the\n fields could not be reached as a whole. aria-busy holds the announcement back while a\n property is being persisted. -->\n <form\n [formRoot]=\"profileForm\"\n [attr.aria-label]=\"'userProfile.profileFields' | transloco\"\n [attr.aria-busy]=\"profileForm().submitting() ? 'true' : null\">\n @for (\n property of properties();\n track property.category + \".\" + property.key\n ) {\n @let category = property.category;\n @let key = property.key;\n @let field = profileForm[category][key];\n @let label =\n property.translated\n ? (\"userProfile.data.\" + key | transloco)\n : key;\n @let editing = isEditing(category, key);\n @let invalid = showError(field);\n <!-- role=\"group\" named by the label: without it the four action buttons of a row are\n announced out of context, as a flat run of \"Edit …\" / \"Delete …\" with nothing tying\n them to the property they belong to. -->\n <div\n role=\"group\"\n [attr.aria-labelledby]=\"labelId(category, key)\"\n class=\"flex flex-col gap-(--spacing-3xs)\">\n <label\n [id]=\"labelId(category, key)\"\n [for]=\"fieldId(category, key)\"\n [class]=\"labelClass\"\n >{{ label }}</label\n >\n <div class=\"group flex flex-row\">\n <div class=\"min-w-0 grow\">\n <textarea-group\n resize=\"auto\"\n [maxRows]=\"3\"\n [scheme]=\"invalid ? 'error' : 'default'\">\n <!-- No [attr.readonly] here: [formField] writes the readonly attribute itself from\n the field state, which the schema's readonly() rule drives. The native\n readonly attribute is what exposes aria-readonly — do not duplicate it. -->\n <textarea\n textarea-control\n rows=\"1\"\n [id]=\"fieldId(category, key)\"\n [formField]=\"field\"\n [attr.placeholder]=\"'userProfile.notDefined' | transloco\"\n [attr.aria-invalid]=\"invalid ? 'true' : null\"\n [attr.aria-describedby]=\"\n invalid ? errorId(category, key) : null\n \"\n [attr.aria-keyshortcuts]=\"editing ? 'Escape' : null\"\n (focus)=\"onFieldFocus($event)\"\n (keydown.escape)=\"onCancelEdit(category, key)\"></textarea>\n </textarea-group>\n </div>\n <!-- group-focus-within, and not only group-hover: `invisible` takes the buttons out of\n the tab order, so without it editing or deleting a property is unreachable with a\n keyboard. Focusing the field of the row reveals its actions. -->\n @if (editing) {\n <button\n type=\"submit\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n class=\"visible mx-2 cursor-pointer sm:invisible sm:group-focus-within:visible sm:group-hover:visible\"\n [disabled]=\"profileForm().submitting()\"\n [title]=\"'userProfile.saveProperty' | transloco\"\n [attr.aria-label]=\"\n 'userProfile.savePropertyNamed'\n | transloco: { property: label }\n \">\n <save-icon />\n </button>\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n class=\"visible cursor-pointer sm:invisible sm:group-focus-within:visible sm:group-hover:visible\"\n [title]=\"'userProfile.cancelEdit' | transloco\"\n [attr.aria-label]=\"\n 'userProfile.cancelEditNamed'\n | transloco: { property: label }\n \"\n (click)=\"onCancelEdit(category, key)\">\n <UndoIcon />\n </button>\n } @else {\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n [id]=\"editButtonId(category, key)\"\n class=\"visible mx-2 cursor-pointer sm:invisible sm:group-focus-within:visible sm:group-hover:visible\"\n [title]=\"'userProfile.editProperty' | transloco\"\n [attr.aria-label]=\"\n 'userProfile.editPropertyNamed'\n | transloco: { property: label }\n \"\n (click)=\"onEdit(category, key)\">\n <EditIcon />\n </button>\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n class=\"text-destructive visible cursor-pointer sm:invisible sm:group-focus-within:visible sm:group-hover:visible\"\n [title]=\"'userProfile.deleteProperty' | transloco\"\n [attr.aria-label]=\"\n 'userProfile.deletePropertyNamed'\n | transloco: { property: label }\n \"\n (click)=\"onDeleteData(category, key)\">\n <TrashIcon />\n </button>\n }\n </div>\n @if (invalid) {\n <p\n [id]=\"errorId(category, key)\"\n role=\"alert\"\n [class]=\"errorClass\">\n {{ errorKey(field) | transloco }}\n </p>\n }\n </div>\n }\n </form>\n <!-- Always in the DOM, and only its text changes: a live region inserted at the same time as\n its content is not reliably announced. `sr-only` — the transitions it describes are\n already visible on screen. -->\n <p class=\"sr-only\" role=\"status\" aria-live=\"polite\">\n {{ statusMessage() }}\n </p>\n </div>\n }\n </div>\n} @else if (userProfileResource.isLoading()) {\n <p role=\"status\">{{ \"userProfile.loading\" | transloco }}</p>\n} @else if (userProfileResource.error()) {\n <p role=\"alert\">{{ \"userProfile.loadError\" | transloco }}</p>\n}\n\n<!-- AVATAR UPLOAD — `hidden` keeps it out of both the tab order and the accessibility tree, which is\n intended: it is opened by the avatar button above. It also sits outside the <form>. -->\n<input\n #avatarInput\n class=\"hidden\"\n type=\"file\"\n accept=\"image/*\"\n (change)=\"uploadAvatar($event)\" />\n"
|
|
31542
31511
|
}]
|
|
31543
31512
|
}],
|
|
31544
|
-
ctorParameters: () => []
|
|
31545
|
-
propDecorators: { currentLanguage: [{
|
|
31546
|
-
type: i0.Input,
|
|
31547
|
-
args: [{
|
|
31548
|
-
isSignal: true,
|
|
31549
|
-
alias: "currentLanguage",
|
|
31550
|
-
required: false
|
|
31551
|
-
}]
|
|
31552
|
-
}, {
|
|
31553
|
-
type: i0.Output,
|
|
31554
|
-
args: ["currentLanguageChange"]
|
|
31555
|
-
}] }
|
|
31513
|
+
ctorParameters: () => []
|
|
31556
31514
|
});
|
|
31557
31515
|
function toRecord(keys, source) {
|
|
31558
31516
|
return Object.fromEntries(keys.map((key) => [key, String(source?.[key] ?? "")]));
|
|
@@ -31564,92 +31522,732 @@ function splitPropertyId(id) {
|
|
|
31564
31522
|
key: id.slice(separator + 1)
|
|
31565
31523
|
};
|
|
31566
31524
|
}
|
|
31567
|
-
var
|
|
31568
|
-
|
|
31569
|
-
|
|
31570
|
-
|
|
31571
|
-
|
|
31525
|
+
var SettingsAccountPanelComponent = class SettingsAccountPanelComponent {
|
|
31526
|
+
principalStore = inject(PrincipalStore);
|
|
31527
|
+
appStore = inject(AppStore);
|
|
31528
|
+
client = inject(ATOMIC_CLIENT);
|
|
31529
|
+
userOverride = inject(UserOverrideService);
|
|
31530
|
+
injector = inject(Injector);
|
|
31531
|
+
profileEnabled = computed(() => this.appStore.general()?.features?.userProfile?.enabled ?? false, ...ngDevMode ? [{ debugName: "profileEnabled" }] : /* istanbul ignore next */ []);
|
|
31532
|
+
username = computed(() => this.principalStore.name() || this.principalStore.email() || null, ...ngDevMode ? [{ debugName: "username" }] : /* istanbul ignore next */ []);
|
|
31533
|
+
allowChangePassword = computed(() => {
|
|
31534
|
+
const useCredentials = this.client.config.authMode?.kind === "credentials";
|
|
31535
|
+
const { allowChangePassword = false } = this.appStore.general()?.features || {};
|
|
31536
|
+
return allowChangePassword && useCredentials && (this.principalStore.editablePartition?.() || false);
|
|
31537
|
+
}, ...ngDevMode ? [{ debugName: "allowChangePassword" }] : /* istanbul ignore next */ []);
|
|
31538
|
+
resetSettings() {
|
|
31539
|
+
ResetUserSettings.call(void 0, { injector: this.injector });
|
|
31540
|
+
}
|
|
31541
|
+
overrideUser() {
|
|
31542
|
+
OverrideUser.call(void 0, { injector: this.injector });
|
|
31543
|
+
}
|
|
31544
|
+
revertOverride() {
|
|
31545
|
+
this.userOverride.override();
|
|
31572
31546
|
}
|
|
31573
31547
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
31574
31548
|
minVersion: "12.0.0",
|
|
31575
31549
|
version: "22.2.1",
|
|
31576
31550
|
ngImport: i0,
|
|
31577
|
-
type:
|
|
31551
|
+
type: SettingsAccountPanelComponent,
|
|
31578
31552
|
deps: [],
|
|
31579
31553
|
target: i0.ɵɵFactoryTarget.Component
|
|
31580
31554
|
});
|
|
31581
31555
|
static ɵcmp = i0.ɵɵngDeclareComponent({
|
|
31582
|
-
minVersion: "17.
|
|
31556
|
+
minVersion: "17.0.0",
|
|
31583
31557
|
version: "22.2.1",
|
|
31584
|
-
type:
|
|
31558
|
+
type: SettingsAccountPanelComponent,
|
|
31585
31559
|
isStandalone: true,
|
|
31586
|
-
selector: "
|
|
31587
|
-
providers: [provideTranslocoScope("
|
|
31588
|
-
viewQueries: [{
|
|
31589
|
-
propertyName: "dialog",
|
|
31590
|
-
first: true,
|
|
31591
|
-
predicate: DialogComponent,
|
|
31592
|
-
descendants: true,
|
|
31593
|
-
isSignal: true
|
|
31594
|
-
}],
|
|
31560
|
+
selector: "settings-account-panel",
|
|
31561
|
+
providers: [provideTranslocoScope("settings")],
|
|
31595
31562
|
ngImport: i0,
|
|
31596
31563
|
template: `
|
|
31597
|
-
<
|
|
31598
|
-
|
|
31599
|
-
|
|
31600
|
-
|
|
31601
|
-
|
|
31564
|
+
<h2 class="text-(length:--size-2xl) font-semibold">{{ "settings.account.title" | transloco }}</h2>
|
|
31565
|
+
<p class="text-(--font-primary-muted)">{{ "settings.account.description" | transloco }}</p>
|
|
31566
|
+
|
|
31567
|
+
@if (profileEnabled()) {
|
|
31568
|
+
<user-profile-form class="mt-(--spacing-md) min-h-0 flex-1" />
|
|
31569
|
+
} @else if (allowChangePassword()) {
|
|
31570
|
+
<!-- No profile to show: the password is all the account offers, as it was in the user menu. -->
|
|
31571
|
+
<change-password
|
|
31572
|
+
class="mt-(--spacing-md)"
|
|
31573
|
+
[surface]="false"
|
|
31574
|
+
[username]="username()"
|
|
31575
|
+
[redirectAfterSuccess]="false"
|
|
31576
|
+
[redirectAfterCancel]="false" />
|
|
31577
|
+
}
|
|
31602
31578
|
|
|
31603
|
-
|
|
31604
|
-
|
|
31605
|
-
|
|
31606
|
-
|
|
31579
|
+
<!-- Account-wide actions, one row each: what it does on the left, the action on the right. -->
|
|
31580
|
+
<div class="mt-(--spacing-md) flex flex-col border-t border-(--stroke-primary-light)">
|
|
31581
|
+
@if (principalStore.isOverridingUser()) {
|
|
31582
|
+
<div class="flex items-center justify-between gap-(--spacing-md) border-b border-(--stroke-primary-light) py-(--spacing-md)">
|
|
31583
|
+
<div>
|
|
31584
|
+
<p id="settings-revert-override-title" class="font-semibold">{{ "settings.account.revertOverride.title" | transloco }}</p>
|
|
31585
|
+
<p id="settings-revert-override-description" class="text-(--font-primary-muted)">{{ "settings.account.revertOverride.description" | transloco }}</p>
|
|
31586
|
+
</div>
|
|
31587
|
+
<button type="button" variant="secondary" class="shrink-0" aria-describedby="settings-revert-override-description" (click)="revertOverride()">
|
|
31588
|
+
{{ "settings.account.revertOverride.action" | transloco }}
|
|
31589
|
+
</button>
|
|
31590
|
+
</div>
|
|
31591
|
+
} @else if (principalStore.allowUserOverride()) {
|
|
31592
|
+
<div class="flex items-center justify-between gap-(--spacing-md) border-b border-(--stroke-primary-light) py-(--spacing-md)">
|
|
31593
|
+
<div>
|
|
31594
|
+
<p id="settings-override-title" class="font-semibold">{{ "settings.account.overrideUser.title" | transloco }}</p>
|
|
31595
|
+
<p id="settings-override-description" class="text-(--font-primary-muted)">{{ "settings.account.overrideUser.description" | transloco }}</p>
|
|
31596
|
+
</div>
|
|
31597
|
+
<button type="button" variant="secondary" class="shrink-0" aria-describedby="settings-override-description" (click)="overrideUser()">
|
|
31598
|
+
{{ "settings.account.overrideUser.action" | transloco }}
|
|
31599
|
+
</button>
|
|
31600
|
+
</div>
|
|
31601
|
+
}
|
|
31602
|
+
|
|
31603
|
+
<div class="flex items-center justify-between gap-(--spacing-md) py-(--spacing-md)">
|
|
31604
|
+
<div>
|
|
31605
|
+
<p id="settings-clear-data-title" class="font-semibold">{{ "settings.account.clearData.title" | transloco }}</p>
|
|
31606
|
+
<p id="settings-clear-data-description" class="text-(--font-primary-muted)">{{ "settings.account.clearData.description" | transloco }}</p>
|
|
31607
|
+
</div>
|
|
31608
|
+
<!-- Destructive: the secondary variant re-coloured with the error tokens. Every state of the
|
|
31609
|
+
variant (hover, active, focus) sets its own colours, so each one is overridden here — a
|
|
31610
|
+
plain border/text colour would only win while the button is at rest. -->
|
|
31611
|
+
<button
|
|
31612
|
+
type="button"
|
|
31613
|
+
variant="secondary"
|
|
31614
|
+
class="shrink-0 border-(--stroke-error-base) text-(--font-error-base) enabled:hover:bg-(--bg-error-lightest) enabled:hover:text-(--font-error-base) enabled:active:border-(--stroke-error-base) enabled:active:bg-(--bg-error-lightest) enabled:active:text-(--font-error-base) enabled:active:shadow-[inset_0_0_0_1px_var(--stroke-error-base)] enabled:focus-visible:border-(--stroke-error-base) enabled:focus-visible:text-(--font-error-base)"
|
|
31615
|
+
aria-describedby="settings-clear-data-description"
|
|
31616
|
+
(click)="resetSettings()">
|
|
31617
|
+
{{ "settings.account.clearData.action" | transloco }}
|
|
31618
|
+
</button>
|
|
31619
|
+
</div>
|
|
31607
31620
|
</div>
|
|
31608
31621
|
`,
|
|
31609
31622
|
isInline: true,
|
|
31610
31623
|
dependencies: [
|
|
31611
31624
|
{
|
|
31612
|
-
kind: "
|
|
31613
|
-
type:
|
|
31614
|
-
selector: "
|
|
31615
|
-
inputs: [
|
|
31616
|
-
|
|
31617
|
-
|
|
31625
|
+
kind: "directive",
|
|
31626
|
+
type: ButtonComponent,
|
|
31627
|
+
selector: "button",
|
|
31628
|
+
inputs: [
|
|
31629
|
+
"class",
|
|
31630
|
+
"variant",
|
|
31631
|
+
"scheme",
|
|
31632
|
+
"iconOnly",
|
|
31633
|
+
"size"
|
|
31634
|
+
]
|
|
31618
31635
|
},
|
|
31619
31636
|
{
|
|
31620
31637
|
kind: "component",
|
|
31621
|
-
type:
|
|
31622
|
-
selector: "
|
|
31623
|
-
inputs: ["closeLabel", "class"]
|
|
31638
|
+
type: UserProfileFormComponent,
|
|
31639
|
+
selector: "user-profile-form, UserProfileForm, userprofileform"
|
|
31624
31640
|
},
|
|
31625
31641
|
{
|
|
31626
|
-
kind: "
|
|
31627
|
-
type:
|
|
31628
|
-
selector: "
|
|
31642
|
+
kind: "component",
|
|
31643
|
+
type: ChangePasswordComponent,
|
|
31644
|
+
selector: "change-password, ChangePassword, changepassword",
|
|
31629
31645
|
inputs: [
|
|
31630
|
-
"
|
|
31631
|
-
"
|
|
31632
|
-
"
|
|
31633
|
-
|
|
31646
|
+
"username",
|
|
31647
|
+
"alert",
|
|
31648
|
+
"redirectAfterSuccess",
|
|
31649
|
+
"redirectAfterCancel",
|
|
31650
|
+
"surface",
|
|
31651
|
+
"passwords"
|
|
31652
|
+
],
|
|
31653
|
+
outputs: [
|
|
31654
|
+
"success",
|
|
31655
|
+
"cancel",
|
|
31656
|
+
"passwordsChange"
|
|
31657
|
+
]
|
|
31658
|
+
},
|
|
31659
|
+
{
|
|
31660
|
+
kind: "pipe",
|
|
31661
|
+
type: TranslocoPipe,
|
|
31662
|
+
name: "transloco"
|
|
31663
|
+
}
|
|
31664
|
+
]
|
|
31665
|
+
});
|
|
31666
|
+
};
|
|
31667
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
31668
|
+
minVersion: "12.0.0",
|
|
31669
|
+
version: "22.2.1",
|
|
31670
|
+
ngImport: i0,
|
|
31671
|
+
type: SettingsAccountPanelComponent,
|
|
31672
|
+
decorators: [{
|
|
31673
|
+
type: Component,
|
|
31674
|
+
args: [{
|
|
31675
|
+
selector: "settings-account-panel",
|
|
31676
|
+
imports: [
|
|
31677
|
+
TranslocoPipe,
|
|
31678
|
+
ButtonComponent,
|
|
31679
|
+
UserProfileFormComponent,
|
|
31680
|
+
ChangePasswordComponent
|
|
31681
|
+
],
|
|
31682
|
+
providers: [provideTranslocoScope("settings")],
|
|
31683
|
+
template: `
|
|
31684
|
+
<h2 class="text-(length:--size-2xl) font-semibold">{{ "settings.account.title" | transloco }}</h2>
|
|
31685
|
+
<p class="text-(--font-primary-muted)">{{ "settings.account.description" | transloco }}</p>
|
|
31686
|
+
|
|
31687
|
+
@if (profileEnabled()) {
|
|
31688
|
+
<user-profile-form class="mt-(--spacing-md) min-h-0 flex-1" />
|
|
31689
|
+
} @else if (allowChangePassword()) {
|
|
31690
|
+
<!-- No profile to show: the password is all the account offers, as it was in the user menu. -->
|
|
31691
|
+
<change-password
|
|
31692
|
+
class="mt-(--spacing-md)"
|
|
31693
|
+
[surface]="false"
|
|
31694
|
+
[username]="username()"
|
|
31695
|
+
[redirectAfterSuccess]="false"
|
|
31696
|
+
[redirectAfterCancel]="false" />
|
|
31697
|
+
}
|
|
31698
|
+
|
|
31699
|
+
<!-- Account-wide actions, one row each: what it does on the left, the action on the right. -->
|
|
31700
|
+
<div class="mt-(--spacing-md) flex flex-col border-t border-(--stroke-primary-light)">
|
|
31701
|
+
@if (principalStore.isOverridingUser()) {
|
|
31702
|
+
<div class="flex items-center justify-between gap-(--spacing-md) border-b border-(--stroke-primary-light) py-(--spacing-md)">
|
|
31703
|
+
<div>
|
|
31704
|
+
<p id="settings-revert-override-title" class="font-semibold">{{ "settings.account.revertOverride.title" | transloco }}</p>
|
|
31705
|
+
<p id="settings-revert-override-description" class="text-(--font-primary-muted)">{{ "settings.account.revertOverride.description" | transloco }}</p>
|
|
31706
|
+
</div>
|
|
31707
|
+
<button type="button" variant="secondary" class="shrink-0" aria-describedby="settings-revert-override-description" (click)="revertOverride()">
|
|
31708
|
+
{{ "settings.account.revertOverride.action" | transloco }}
|
|
31709
|
+
</button>
|
|
31710
|
+
</div>
|
|
31711
|
+
} @else if (principalStore.allowUserOverride()) {
|
|
31712
|
+
<div class="flex items-center justify-between gap-(--spacing-md) border-b border-(--stroke-primary-light) py-(--spacing-md)">
|
|
31713
|
+
<div>
|
|
31714
|
+
<p id="settings-override-title" class="font-semibold">{{ "settings.account.overrideUser.title" | transloco }}</p>
|
|
31715
|
+
<p id="settings-override-description" class="text-(--font-primary-muted)">{{ "settings.account.overrideUser.description" | transloco }}</p>
|
|
31716
|
+
</div>
|
|
31717
|
+
<button type="button" variant="secondary" class="shrink-0" aria-describedby="settings-override-description" (click)="overrideUser()">
|
|
31718
|
+
{{ "settings.account.overrideUser.action" | transloco }}
|
|
31719
|
+
</button>
|
|
31720
|
+
</div>
|
|
31721
|
+
}
|
|
31722
|
+
|
|
31723
|
+
<div class="flex items-center justify-between gap-(--spacing-md) py-(--spacing-md)">
|
|
31724
|
+
<div>
|
|
31725
|
+
<p id="settings-clear-data-title" class="font-semibold">{{ "settings.account.clearData.title" | transloco }}</p>
|
|
31726
|
+
<p id="settings-clear-data-description" class="text-(--font-primary-muted)">{{ "settings.account.clearData.description" | transloco }}</p>
|
|
31727
|
+
</div>
|
|
31728
|
+
<!-- Destructive: the secondary variant re-coloured with the error tokens. Every state of the
|
|
31729
|
+
variant (hover, active, focus) sets its own colours, so each one is overridden here — a
|
|
31730
|
+
plain border/text colour would only win while the button is at rest. -->
|
|
31731
|
+
<button
|
|
31732
|
+
type="button"
|
|
31733
|
+
variant="secondary"
|
|
31734
|
+
class="shrink-0 border-(--stroke-error-base) text-(--font-error-base) enabled:hover:bg-(--bg-error-lightest) enabled:hover:text-(--font-error-base) enabled:active:border-(--stroke-error-base) enabled:active:bg-(--bg-error-lightest) enabled:active:text-(--font-error-base) enabled:active:shadow-[inset_0_0_0_1px_var(--stroke-error-base)] enabled:focus-visible:border-(--stroke-error-base) enabled:focus-visible:text-(--font-error-base)"
|
|
31735
|
+
aria-describedby="settings-clear-data-description"
|
|
31736
|
+
(click)="resetSettings()">
|
|
31737
|
+
{{ "settings.account.clearData.action" | transloco }}
|
|
31738
|
+
</button>
|
|
31739
|
+
</div>
|
|
31740
|
+
</div>
|
|
31741
|
+
`
|
|
31742
|
+
}]
|
|
31743
|
+
}]
|
|
31744
|
+
});
|
|
31745
|
+
const THEMES$1 = [
|
|
31746
|
+
"light",
|
|
31747
|
+
"dark",
|
|
31748
|
+
"system"
|
|
31749
|
+
];
|
|
31750
|
+
const LANGUAGES = [
|
|
31751
|
+
"en",
|
|
31752
|
+
"fr",
|
|
31753
|
+
"de"
|
|
31754
|
+
];
|
|
31755
|
+
const isTheme = (value) => THEMES$1.includes(value);
|
|
31756
|
+
const isSupportedLanguage = (value) => LANGUAGES.includes(value);
|
|
31757
|
+
var SettingsAppearancePanelComponent = class SettingsAppearancePanelComponent {
|
|
31758
|
+
userSettingsStore = inject(UserSettingsStore);
|
|
31759
|
+
transloco = inject(TranslocoService);
|
|
31760
|
+
document = inject(DOCUMENT);
|
|
31761
|
+
lang = toSignal(this.transloco.langChanges$, { initialValue: this.transloco.getActiveLang() });
|
|
31762
|
+
loadedTranslations = toSignal(this.transloco.events$.pipe(filter((event) => event.type === "translationLoadSuccess"), scan((count) => count + 1, 0)), { initialValue: 0 });
|
|
31763
|
+
themeCodes = [...THEMES$1];
|
|
31764
|
+
languageCodes = [...LANGUAGES];
|
|
31765
|
+
themeIcons = {
|
|
31766
|
+
light: SunBrightIcon,
|
|
31767
|
+
dark: MoonIcon,
|
|
31768
|
+
system: DesktopIcon
|
|
31769
|
+
};
|
|
31770
|
+
languageIcons = {
|
|
31771
|
+
en: FlagEnglishIcon,
|
|
31772
|
+
fr: FlagFrenchIcon,
|
|
31773
|
+
de: FlagGermanIcon
|
|
31774
|
+
};
|
|
31775
|
+
languageLabels = {
|
|
31776
|
+
en: "English",
|
|
31777
|
+
fr: "Français",
|
|
31778
|
+
de: "Deutsch"
|
|
31779
|
+
};
|
|
31780
|
+
currentTheme = computed(() => this.userSettingsStore.userTheme() ?? "system", ...ngDevMode ? [{ debugName: "currentTheme" }] : /* istanbul ignore next */ []);
|
|
31781
|
+
currentLanguage = computed(() => this.lang(), ...ngDevMode ? [{ debugName: "currentLanguage" }] : /* istanbul ignore next */ []);
|
|
31782
|
+
themeLabel = (code) => {
|
|
31783
|
+
this.lang();
|
|
31784
|
+
this.loadedTranslations();
|
|
31785
|
+
return isTheme(code) ? this.transloco.translate(`settings.appearance.${code}Mode`) : "";
|
|
31786
|
+
};
|
|
31787
|
+
languageLabel = (code) => isSupportedLanguage(code) ? this.languageLabels[code] : code ?? "";
|
|
31788
|
+
themeIcon(code) {
|
|
31789
|
+
return isTheme(code) ? this.themeIcons[code] : null;
|
|
31790
|
+
}
|
|
31791
|
+
languageIcon(code) {
|
|
31792
|
+
return isSupportedLanguage(code) ? this.languageIcons[code] : null;
|
|
31793
|
+
}
|
|
31794
|
+
async onThemeChange(code) {
|
|
31795
|
+
if (!isTheme(code)) return;
|
|
31796
|
+
try {
|
|
31797
|
+
await this.userSettingsStore.setUserTheme(code);
|
|
31798
|
+
} catch (err) {
|
|
31799
|
+
error("set user theme failed", err);
|
|
31800
|
+
return;
|
|
31801
|
+
}
|
|
31802
|
+
const dark = code === "dark" || code === "system" && window.matchMedia?.("(prefers-color-scheme: dark)").matches;
|
|
31803
|
+
this.document.documentElement.classList.toggle("dark", dark);
|
|
31804
|
+
}
|
|
31805
|
+
onLanguageChange(code) {
|
|
31806
|
+
if (!isSupportedLanguage(code)) return;
|
|
31807
|
+
this.userSettingsStore.updateLanguage(code).catch((err) => error("update language failed", err));
|
|
31808
|
+
if (this.transloco.getActiveLang() !== code) this.transloco.setActiveLang(code);
|
|
31809
|
+
}
|
|
31810
|
+
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
31811
|
+
minVersion: "12.0.0",
|
|
31812
|
+
version: "22.2.1",
|
|
31813
|
+
ngImport: i0,
|
|
31814
|
+
type: SettingsAppearancePanelComponent,
|
|
31815
|
+
deps: [],
|
|
31816
|
+
target: i0.ɵɵFactoryTarget.Component
|
|
31817
|
+
});
|
|
31818
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({
|
|
31819
|
+
minVersion: "14.0.0",
|
|
31820
|
+
version: "22.2.1",
|
|
31821
|
+
type: SettingsAppearancePanelComponent,
|
|
31822
|
+
isStandalone: true,
|
|
31823
|
+
selector: "settings-appearance-panel",
|
|
31824
|
+
providers: [provideTranslocoScope("settings")],
|
|
31825
|
+
ngImport: i0,
|
|
31826
|
+
template: `
|
|
31827
|
+
<h2 class="text-(length:--size-2xl) font-semibold">{{ "settings.appearance.title" | transloco }}</h2>
|
|
31828
|
+
<p class="text-(--font-primary-muted)">{{ "settings.appearance.description" | transloco }}</p>
|
|
31829
|
+
|
|
31830
|
+
<div class="mt-(--spacing-lg) flex items-start justify-between gap-(--spacing-md)">
|
|
31831
|
+
<div>
|
|
31832
|
+
<label id="settings-theme-label" class="font-semibold" for="settings-theme">{{ "settings.appearance.theme" | transloco }}</label>
|
|
31833
|
+
<p id="settings-theme-description" class="text-(--font-primary-muted)">{{ "settings.appearance.themeDescription" | transloco }}</p>
|
|
31834
|
+
</div>
|
|
31835
|
+
<SelectMenu class="w-fit shrink-0" size="md" [options]="themeCodes" [label]="themeLabel" [value]="currentTheme()" (valueChange)="onThemeChange($event)">
|
|
31836
|
+
<button selectTrigger type="button" id="settings-theme" class="select-trigger" aria-describedby="settings-theme-description">
|
|
31837
|
+
<ng-container *ngComponentOutlet="themeIcon(currentTheme())" />
|
|
31838
|
+
<span class="select-trigger-label">{{ themeLabel(currentTheme()) }}</span>
|
|
31839
|
+
<chevron-down-icon class="select-chevron" />
|
|
31840
|
+
</button>
|
|
31841
|
+
<!-- The default option rendering is the label alone: the icon needs its own template. -->
|
|
31842
|
+
<ng-template #selectOption let-option>
|
|
31843
|
+
<span class="flex items-center gap-(--spacing-xs)">
|
|
31844
|
+
<ng-container *ngComponentOutlet="themeIcon(option)" />
|
|
31845
|
+
{{ themeLabel(option) }}
|
|
31846
|
+
</span>
|
|
31847
|
+
</ng-template>
|
|
31848
|
+
</SelectMenu>
|
|
31849
|
+
</div>
|
|
31850
|
+
|
|
31851
|
+
<hr class="my-(--spacing-md) border-(--stroke-primary-light)" />
|
|
31852
|
+
|
|
31853
|
+
<div class="flex items-start justify-between gap-(--spacing-md)">
|
|
31854
|
+
<div>
|
|
31855
|
+
<label id="settings-language-label" class="font-semibold" for="settings-language">{{ "settings.appearance.language" | transloco }}</label>
|
|
31856
|
+
<p id="settings-language-description" class="text-(--font-primary-muted)">{{ "settings.appearance.languageDescription" | transloco }}</p>
|
|
31857
|
+
</div>
|
|
31858
|
+
<SelectMenu
|
|
31859
|
+
class="w-fit shrink-0"
|
|
31860
|
+
size="md"
|
|
31861
|
+
[options]="languageCodes"
|
|
31862
|
+
[label]="languageLabel"
|
|
31863
|
+
[value]="currentLanguage()"
|
|
31864
|
+
(valueChange)="onLanguageChange($event)">
|
|
31865
|
+
<button selectTrigger type="button" id="settings-language" class="select-trigger" aria-describedby="settings-language-description">
|
|
31866
|
+
<ng-container *ngComponentOutlet="languageIcon(currentLanguage())" />
|
|
31867
|
+
<span class="select-trigger-label">{{ languageLabel(currentLanguage()) }}</span>
|
|
31868
|
+
<chevron-down-icon class="select-chevron" />
|
|
31869
|
+
</button>
|
|
31870
|
+
<ng-template #selectOption let-option>
|
|
31871
|
+
<span class="flex items-center gap-(--spacing-xs)">
|
|
31872
|
+
<ng-container *ngComponentOutlet="languageIcon(option)" />
|
|
31873
|
+
{{ languageLabel(option) }}
|
|
31874
|
+
</span>
|
|
31875
|
+
</ng-template>
|
|
31876
|
+
</SelectMenu>
|
|
31877
|
+
</div>
|
|
31878
|
+
`,
|
|
31879
|
+
isInline: true,
|
|
31880
|
+
dependencies: [
|
|
31881
|
+
{
|
|
31882
|
+
kind: "directive",
|
|
31883
|
+
type: NgComponentOutlet,
|
|
31884
|
+
selector: "[ngComponentOutlet]",
|
|
31885
|
+
inputs: [
|
|
31886
|
+
"ngComponentOutlet",
|
|
31887
|
+
"ngComponentOutletInputs",
|
|
31888
|
+
"ngComponentOutletInjector",
|
|
31889
|
+
"ngComponentOutletEnvironmentInjector",
|
|
31890
|
+
"ngComponentOutletContent",
|
|
31891
|
+
"ngComponentOutletNgModule"
|
|
31892
|
+
],
|
|
31893
|
+
exportAs: ["ngComponentOutlet"]
|
|
31894
|
+
},
|
|
31895
|
+
{
|
|
31896
|
+
kind: "component",
|
|
31897
|
+
type: SelectComponent,
|
|
31898
|
+
selector: "SelectMenu,select-menu",
|
|
31899
|
+
inputs: [
|
|
31900
|
+
"value",
|
|
31901
|
+
"options",
|
|
31902
|
+
"placement",
|
|
31903
|
+
"size",
|
|
31904
|
+
"fieldLabel",
|
|
31905
|
+
"aria-label",
|
|
31906
|
+
"placeholder",
|
|
31907
|
+
"hint",
|
|
31908
|
+
"disabled",
|
|
31909
|
+
"class",
|
|
31910
|
+
"selectionMode",
|
|
31911
|
+
"multiple",
|
|
31912
|
+
"wrap",
|
|
31913
|
+
"key",
|
|
31914
|
+
"label",
|
|
31915
|
+
"optionDisabled",
|
|
31916
|
+
"noOptionsText",
|
|
31917
|
+
"contentClass"
|
|
31918
|
+
],
|
|
31919
|
+
outputs: ["valueChange", "touch"],
|
|
31920
|
+
exportAs: ["select"]
|
|
31634
31921
|
},
|
|
31635
31922
|
{
|
|
31636
31923
|
kind: "directive",
|
|
31637
|
-
type:
|
|
31638
|
-
selector: "
|
|
31924
|
+
type: SelectTriggerDirective,
|
|
31925
|
+
selector: "[selectTrigger]",
|
|
31926
|
+
exportAs: ["selectTrigger"]
|
|
31927
|
+
},
|
|
31928
|
+
{
|
|
31929
|
+
kind: "component",
|
|
31930
|
+
type: ChevronDownIcon,
|
|
31931
|
+
selector: "chevron-down, chevron-down-icon, ChevronDown, ChevronDownIcon",
|
|
31639
31932
|
inputs: ["class"]
|
|
31640
31933
|
},
|
|
31934
|
+
{
|
|
31935
|
+
kind: "pipe",
|
|
31936
|
+
type: TranslocoPipe,
|
|
31937
|
+
name: "transloco"
|
|
31938
|
+
}
|
|
31939
|
+
]
|
|
31940
|
+
});
|
|
31941
|
+
};
|
|
31942
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
31943
|
+
minVersion: "12.0.0",
|
|
31944
|
+
version: "22.2.1",
|
|
31945
|
+
ngImport: i0,
|
|
31946
|
+
type: SettingsAppearancePanelComponent,
|
|
31947
|
+
decorators: [{
|
|
31948
|
+
type: Component,
|
|
31949
|
+
args: [{
|
|
31950
|
+
selector: "settings-appearance-panel",
|
|
31951
|
+
imports: [
|
|
31952
|
+
NgComponentOutlet,
|
|
31953
|
+
TranslocoPipe,
|
|
31954
|
+
SelectComponent,
|
|
31955
|
+
SelectTriggerDirective,
|
|
31956
|
+
ChevronDownIcon
|
|
31957
|
+
],
|
|
31958
|
+
providers: [provideTranslocoScope("settings")],
|
|
31959
|
+
template: `
|
|
31960
|
+
<h2 class="text-(length:--size-2xl) font-semibold">{{ "settings.appearance.title" | transloco }}</h2>
|
|
31961
|
+
<p class="text-(--font-primary-muted)">{{ "settings.appearance.description" | transloco }}</p>
|
|
31962
|
+
|
|
31963
|
+
<div class="mt-(--spacing-lg) flex items-start justify-between gap-(--spacing-md)">
|
|
31964
|
+
<div>
|
|
31965
|
+
<label id="settings-theme-label" class="font-semibold" for="settings-theme">{{ "settings.appearance.theme" | transloco }}</label>
|
|
31966
|
+
<p id="settings-theme-description" class="text-(--font-primary-muted)">{{ "settings.appearance.themeDescription" | transloco }}</p>
|
|
31967
|
+
</div>
|
|
31968
|
+
<SelectMenu class="w-fit shrink-0" size="md" [options]="themeCodes" [label]="themeLabel" [value]="currentTheme()" (valueChange)="onThemeChange($event)">
|
|
31969
|
+
<button selectTrigger type="button" id="settings-theme" class="select-trigger" aria-describedby="settings-theme-description">
|
|
31970
|
+
<ng-container *ngComponentOutlet="themeIcon(currentTheme())" />
|
|
31971
|
+
<span class="select-trigger-label">{{ themeLabel(currentTheme()) }}</span>
|
|
31972
|
+
<chevron-down-icon class="select-chevron" />
|
|
31973
|
+
</button>
|
|
31974
|
+
<!-- The default option rendering is the label alone: the icon needs its own template. -->
|
|
31975
|
+
<ng-template #selectOption let-option>
|
|
31976
|
+
<span class="flex items-center gap-(--spacing-xs)">
|
|
31977
|
+
<ng-container *ngComponentOutlet="themeIcon(option)" />
|
|
31978
|
+
{{ themeLabel(option) }}
|
|
31979
|
+
</span>
|
|
31980
|
+
</ng-template>
|
|
31981
|
+
</SelectMenu>
|
|
31982
|
+
</div>
|
|
31983
|
+
|
|
31984
|
+
<hr class="my-(--spacing-md) border-(--stroke-primary-light)" />
|
|
31985
|
+
|
|
31986
|
+
<div class="flex items-start justify-between gap-(--spacing-md)">
|
|
31987
|
+
<div>
|
|
31988
|
+
<label id="settings-language-label" class="font-semibold" for="settings-language">{{ "settings.appearance.language" | transloco }}</label>
|
|
31989
|
+
<p id="settings-language-description" class="text-(--font-primary-muted)">{{ "settings.appearance.languageDescription" | transloco }}</p>
|
|
31990
|
+
</div>
|
|
31991
|
+
<SelectMenu
|
|
31992
|
+
class="w-fit shrink-0"
|
|
31993
|
+
size="md"
|
|
31994
|
+
[options]="languageCodes"
|
|
31995
|
+
[label]="languageLabel"
|
|
31996
|
+
[value]="currentLanguage()"
|
|
31997
|
+
(valueChange)="onLanguageChange($event)">
|
|
31998
|
+
<button selectTrigger type="button" id="settings-language" class="select-trigger" aria-describedby="settings-language-description">
|
|
31999
|
+
<ng-container *ngComponentOutlet="languageIcon(currentLanguage())" />
|
|
32000
|
+
<span class="select-trigger-label">{{ languageLabel(currentLanguage()) }}</span>
|
|
32001
|
+
<chevron-down-icon class="select-chevron" />
|
|
32002
|
+
</button>
|
|
32003
|
+
<ng-template #selectOption let-option>
|
|
32004
|
+
<span class="flex items-center gap-(--spacing-xs)">
|
|
32005
|
+
<ng-container *ngComponentOutlet="languageIcon(option)" />
|
|
32006
|
+
{{ languageLabel(option) }}
|
|
32007
|
+
</span>
|
|
32008
|
+
</ng-template>
|
|
32009
|
+
</SelectMenu>
|
|
32010
|
+
</div>
|
|
32011
|
+
`
|
|
32012
|
+
}]
|
|
32013
|
+
}]
|
|
32014
|
+
});
|
|
32015
|
+
var SettingsShortcutsPanelComponent = class SettingsShortcutsPanelComponent {
|
|
32016
|
+
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
32017
|
+
minVersion: "12.0.0",
|
|
32018
|
+
version: "22.2.1",
|
|
32019
|
+
ngImport: i0,
|
|
32020
|
+
type: SettingsShortcutsPanelComponent,
|
|
32021
|
+
deps: [],
|
|
32022
|
+
target: i0.ɵɵFactoryTarget.Component
|
|
32023
|
+
});
|
|
32024
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({
|
|
32025
|
+
minVersion: "14.0.0",
|
|
32026
|
+
version: "22.2.1",
|
|
32027
|
+
type: SettingsShortcutsPanelComponent,
|
|
32028
|
+
isStandalone: true,
|
|
32029
|
+
selector: "settings-shortcuts-panel",
|
|
32030
|
+
providers: [provideTranslocoScope("settings")],
|
|
32031
|
+
ngImport: i0,
|
|
32032
|
+
template: `
|
|
32033
|
+
<h2 class="text-(length:--size-2xl) font-semibold">{{ "settings.shortcuts.title" | transloco }}</h2>
|
|
32034
|
+
<p class="mb-(--spacing-md) text-(--font-primary-muted)">{{ "settings.shortcuts.description" | transloco }}</p>
|
|
32035
|
+
<keyboard-shortcuts-list />
|
|
32036
|
+
`,
|
|
32037
|
+
isInline: true,
|
|
32038
|
+
dependencies: [{
|
|
32039
|
+
kind: "component",
|
|
32040
|
+
type: KeyboardShortcutsListComponent,
|
|
32041
|
+
selector: "keyboard-shortcuts-list"
|
|
32042
|
+
}, {
|
|
32043
|
+
kind: "pipe",
|
|
32044
|
+
type: TranslocoPipe,
|
|
32045
|
+
name: "transloco"
|
|
32046
|
+
}]
|
|
32047
|
+
});
|
|
32048
|
+
};
|
|
32049
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
32050
|
+
minVersion: "12.0.0",
|
|
32051
|
+
version: "22.2.1",
|
|
32052
|
+
ngImport: i0,
|
|
32053
|
+
type: SettingsShortcutsPanelComponent,
|
|
32054
|
+
decorators: [{
|
|
32055
|
+
type: Component,
|
|
32056
|
+
args: [{
|
|
32057
|
+
selector: "settings-shortcuts-panel",
|
|
32058
|
+
imports: [TranslocoPipe, KeyboardShortcutsListComponent],
|
|
32059
|
+
providers: [provideTranslocoScope("settings")],
|
|
32060
|
+
template: `
|
|
32061
|
+
<h2 class="text-(length:--size-2xl) font-semibold">{{ "settings.shortcuts.title" | transloco }}</h2>
|
|
32062
|
+
<p class="mb-(--spacing-md) text-(--font-primary-muted)">{{ "settings.shortcuts.description" | transloco }}</p>
|
|
32063
|
+
<keyboard-shortcuts-list />
|
|
32064
|
+
`
|
|
32065
|
+
}]
|
|
32066
|
+
}]
|
|
32067
|
+
});
|
|
32068
|
+
let active;
|
|
32069
|
+
var SettingsDialogComponent = class SettingsDialogComponent {
|
|
32070
|
+
call = injectCallRef();
|
|
32071
|
+
dialog = viewChild(DialogComponent, ...ngDevMode ? [{ debugName: "dialog" }] : /* istanbul ignore next */ []);
|
|
32072
|
+
items = [
|
|
32073
|
+
{
|
|
32074
|
+
id: "account",
|
|
32075
|
+
labelKey: "settings.account.nav"
|
|
32076
|
+
},
|
|
32077
|
+
{
|
|
32078
|
+
id: "appearance",
|
|
32079
|
+
labelKey: "settings.appearance.nav"
|
|
32080
|
+
},
|
|
32081
|
+
{
|
|
32082
|
+
id: "shortcuts",
|
|
32083
|
+
labelKey: "settings.shortcuts.nav"
|
|
32084
|
+
}
|
|
32085
|
+
];
|
|
32086
|
+
panel = signal(this.call.props()?.panel ?? "account", ...ngDevMode ? [{ debugName: "panel" }] : /* istanbul ignore next */ []);
|
|
32087
|
+
duplicate = active !== void 0;
|
|
32088
|
+
constructor() {
|
|
32089
|
+
if (active) {
|
|
32090
|
+
active.panel.set(this.panel());
|
|
32091
|
+
afterNextRender(() => this.call.end("dialog-close"));
|
|
32092
|
+
return;
|
|
32093
|
+
}
|
|
32094
|
+
active = this;
|
|
32095
|
+
inject(DestroyRef).onDestroy(() => {
|
|
32096
|
+
if (active === this) active = void 0;
|
|
32097
|
+
});
|
|
32098
|
+
afterNextRender(() => this.dialog()?.open());
|
|
32099
|
+
}
|
|
32100
|
+
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
32101
|
+
minVersion: "12.0.0",
|
|
32102
|
+
version: "22.2.1",
|
|
32103
|
+
ngImport: i0,
|
|
32104
|
+
type: SettingsDialogComponent,
|
|
32105
|
+
deps: [],
|
|
32106
|
+
target: i0.ɵɵFactoryTarget.Component
|
|
32107
|
+
});
|
|
32108
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({
|
|
32109
|
+
minVersion: "17.0.0",
|
|
32110
|
+
version: "22.2.1",
|
|
32111
|
+
type: SettingsDialogComponent,
|
|
32112
|
+
isStandalone: true,
|
|
32113
|
+
selector: "settings-dialog",
|
|
32114
|
+
providers: [provideTranslocoScope("settings")],
|
|
32115
|
+
viewQueries: [{
|
|
32116
|
+
propertyName: "dialog",
|
|
32117
|
+
first: true,
|
|
32118
|
+
predicate: DialogComponent,
|
|
32119
|
+
descendants: true,
|
|
32120
|
+
isSignal: true
|
|
32121
|
+
}],
|
|
32122
|
+
ngImport: i0,
|
|
32123
|
+
template: `
|
|
32124
|
+
@if (!duplicate) {
|
|
32125
|
+
<div dialog #dialog="dialog" [closeLabel]="'settings.close' | transloco" (closed)="call.end($event)">
|
|
32126
|
+
<DialogContent size="xl" class="h-[min(48rem,calc(100dvh-2rem))] flex-row! items-stretch!">
|
|
32127
|
+
<div class="flex w-64 shrink-0 flex-col gap-(--spacing-3xs) bg-(--layer-page-on-layer) p-(--spacing-sm)">
|
|
32128
|
+
<button
|
|
32129
|
+
type="button"
|
|
32130
|
+
variant="none"
|
|
32131
|
+
class="modal-close mb-(--spacing-sm)"
|
|
32132
|
+
[attr.aria-label]="'settings.close' | transloco"
|
|
32133
|
+
(click)="dialog.close()">
|
|
32134
|
+
<XMarkIcon aria-hidden="true" />
|
|
32135
|
+
</button>
|
|
32136
|
+
|
|
32137
|
+
<!-- Panels are a navigation, not a tablist: each one is a button, the current one is
|
|
32138
|
+
flagged with aria-current, which is what a screen reader reads out. -->
|
|
32139
|
+
<nav [attr.aria-label]="'settings.title' | transloco" class="flex flex-col gap-(--spacing-3xs)">
|
|
32140
|
+
@for (item of items; track item.id) {
|
|
32141
|
+
<button
|
|
32142
|
+
type="button"
|
|
32143
|
+
variant="none"
|
|
32144
|
+
class="flex h-(--row-height-md) w-full cursor-pointer items-center justify-start gap-(--spacing-xs) rounded-(--radius-sm) border-0 bg-transparent px-(--spacing-xs) text-left text-(--layer-page-font) transition-colors hover:bg-(--bg-primary-light) focus-visible:outline-2 focus-visible:outline-offset-0 focus-visible:outline-(--stroke-focus) aria-[current=page]:bg-(--bg-primary-base) aria-[current=page]:text-(--font-neutral-white) aria-[current=page]:hover:bg-(--bg-primary-base)"
|
|
32145
|
+
[attr.aria-current]="panel() === item.id ? 'page' : null"
|
|
32146
|
+
(click)="panel.set(item.id)">
|
|
32147
|
+
@switch (item.id) {
|
|
32148
|
+
@case ("account") {
|
|
32149
|
+
<user-icon aria-hidden="true" />
|
|
32150
|
+
}
|
|
32151
|
+
@case ("appearance") {
|
|
32152
|
+
<palette-icon aria-hidden="true" />
|
|
32153
|
+
}
|
|
32154
|
+
@case ("shortcuts") {
|
|
32155
|
+
<keyboard-icon aria-hidden="true" />
|
|
32156
|
+
}
|
|
32157
|
+
}
|
|
32158
|
+
{{ item.labelKey | transloco }}
|
|
32159
|
+
</button>
|
|
32160
|
+
}
|
|
32161
|
+
</nav>
|
|
32162
|
+
</div>
|
|
32163
|
+
|
|
32164
|
+
<div class="m-(--spacing-xs) flex min-w-0 flex-1 flex-col overflow-y-auto rounded-(--radius-lg) bg-(--layer-page-layer) p-(--spacing-xl)">
|
|
32165
|
+
@switch (panel()) {
|
|
32166
|
+
@case ("account") {
|
|
32167
|
+
<settings-account-panel class="flex min-h-0 flex-1 flex-col" />
|
|
32168
|
+
}
|
|
32169
|
+
@case ("appearance") {
|
|
32170
|
+
<settings-appearance-panel />
|
|
32171
|
+
}
|
|
32172
|
+
@case ("shortcuts") {
|
|
32173
|
+
<settings-shortcuts-panel />
|
|
32174
|
+
}
|
|
32175
|
+
}
|
|
32176
|
+
</div>
|
|
32177
|
+
</DialogContent>
|
|
32178
|
+
</div>
|
|
32179
|
+
}
|
|
32180
|
+
`,
|
|
32181
|
+
isInline: true,
|
|
32182
|
+
dependencies: [
|
|
32183
|
+
{
|
|
32184
|
+
kind: "directive",
|
|
32185
|
+
type: ButtonComponent,
|
|
32186
|
+
selector: "button",
|
|
32187
|
+
inputs: [
|
|
32188
|
+
"class",
|
|
32189
|
+
"variant",
|
|
32190
|
+
"scheme",
|
|
32191
|
+
"iconOnly",
|
|
32192
|
+
"size"
|
|
32193
|
+
]
|
|
32194
|
+
},
|
|
31641
32195
|
{
|
|
31642
32196
|
kind: "component",
|
|
31643
|
-
type:
|
|
31644
|
-
selector: "
|
|
31645
|
-
inputs: ["
|
|
31646
|
-
outputs: ["
|
|
32197
|
+
type: DialogComponent,
|
|
32198
|
+
selector: "dialog, [dialog]",
|
|
32199
|
+
inputs: ["closeLabel"],
|
|
32200
|
+
outputs: ["closed"],
|
|
32201
|
+
exportAs: ["dialog"]
|
|
31647
32202
|
},
|
|
31648
32203
|
{
|
|
31649
32204
|
kind: "directive",
|
|
31650
|
-
type:
|
|
31651
|
-
selector: "
|
|
31652
|
-
inputs: [
|
|
32205
|
+
type: DialogContentComponent,
|
|
32206
|
+
selector: "DialogContent",
|
|
32207
|
+
inputs: [
|
|
32208
|
+
"size",
|
|
32209
|
+
"labelledby",
|
|
32210
|
+
"class"
|
|
32211
|
+
]
|
|
32212
|
+
},
|
|
32213
|
+
{
|
|
32214
|
+
kind: "component",
|
|
32215
|
+
type: UserIcon,
|
|
32216
|
+
selector: "user, user-icon, User, UserIcon",
|
|
32217
|
+
inputs: ["class"]
|
|
32218
|
+
},
|
|
32219
|
+
{
|
|
32220
|
+
kind: "component",
|
|
32221
|
+
type: PaletteIcon,
|
|
32222
|
+
selector: "palette, palette-icon, Palette, PaletteIcon",
|
|
32223
|
+
inputs: ["class"]
|
|
32224
|
+
},
|
|
32225
|
+
{
|
|
32226
|
+
kind: "component",
|
|
32227
|
+
type: KeyboardIcon,
|
|
32228
|
+
selector: "keyboard, keyboard-icon, Keyboard, KeyboardIcon",
|
|
32229
|
+
inputs: ["class"]
|
|
32230
|
+
},
|
|
32231
|
+
{
|
|
32232
|
+
kind: "component",
|
|
32233
|
+
type: XMarkIcon,
|
|
32234
|
+
selector: "x-mark, x-mark-icon, XMark, XMarkIcon",
|
|
32235
|
+
inputs: ["class"]
|
|
32236
|
+
},
|
|
32237
|
+
{
|
|
32238
|
+
kind: "component",
|
|
32239
|
+
type: SettingsAccountPanelComponent,
|
|
32240
|
+
selector: "settings-account-panel"
|
|
32241
|
+
},
|
|
32242
|
+
{
|
|
32243
|
+
kind: "component",
|
|
32244
|
+
type: SettingsAppearancePanelComponent,
|
|
32245
|
+
selector: "settings-appearance-panel"
|
|
32246
|
+
},
|
|
32247
|
+
{
|
|
32248
|
+
kind: "component",
|
|
32249
|
+
type: SettingsShortcutsPanelComponent,
|
|
32250
|
+
selector: "settings-shortcuts-panel"
|
|
31653
32251
|
},
|
|
31654
32252
|
{
|
|
31655
32253
|
kind: "pipe",
|
|
@@ -31663,33 +32261,82 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
31663
32261
|
minVersion: "12.0.0",
|
|
31664
32262
|
version: "22.2.1",
|
|
31665
32263
|
ngImport: i0,
|
|
31666
|
-
type:
|
|
32264
|
+
type: SettingsDialogComponent,
|
|
31667
32265
|
decorators: [{
|
|
31668
32266
|
type: Component,
|
|
31669
32267
|
args: [{
|
|
31670
|
-
selector: "
|
|
32268
|
+
selector: "settings-dialog",
|
|
31671
32269
|
imports: [
|
|
32270
|
+
TranslocoPipe,
|
|
32271
|
+
ButtonComponent,
|
|
31672
32272
|
DialogComponent,
|
|
31673
|
-
DialogHeaderComponent,
|
|
31674
32273
|
DialogContentComponent,
|
|
31675
|
-
|
|
31676
|
-
|
|
31677
|
-
|
|
31678
|
-
|
|
32274
|
+
UserIcon,
|
|
32275
|
+
PaletteIcon,
|
|
32276
|
+
KeyboardIcon,
|
|
32277
|
+
XMarkIcon,
|
|
32278
|
+
SettingsAccountPanelComponent,
|
|
32279
|
+
SettingsAppearancePanelComponent,
|
|
32280
|
+
SettingsShortcutsPanelComponent
|
|
31679
32281
|
],
|
|
31680
|
-
providers: [provideTranslocoScope("
|
|
32282
|
+
providers: [provideTranslocoScope("settings")],
|
|
31681
32283
|
template: `
|
|
31682
|
-
|
|
31683
|
-
<
|
|
31684
|
-
<
|
|
31685
|
-
<
|
|
31686
|
-
|
|
32284
|
+
@if (!duplicate) {
|
|
32285
|
+
<div dialog #dialog="dialog" [closeLabel]="'settings.close' | transloco" (closed)="call.end($event)">
|
|
32286
|
+
<DialogContent size="xl" class="h-[min(48rem,calc(100dvh-2rem))] flex-row! items-stretch!">
|
|
32287
|
+
<div class="flex w-64 shrink-0 flex-col gap-(--spacing-3xs) bg-(--layer-page-on-layer) p-(--spacing-sm)">
|
|
32288
|
+
<button
|
|
32289
|
+
type="button"
|
|
32290
|
+
variant="none"
|
|
32291
|
+
class="modal-close mb-(--spacing-sm)"
|
|
32292
|
+
[attr.aria-label]="'settings.close' | transloco"
|
|
32293
|
+
(click)="dialog.close()">
|
|
32294
|
+
<XMarkIcon aria-hidden="true" />
|
|
32295
|
+
</button>
|
|
31687
32296
|
|
|
31688
|
-
|
|
31689
|
-
|
|
31690
|
-
|
|
31691
|
-
|
|
31692
|
-
|
|
32297
|
+
<!-- Panels are a navigation, not a tablist: each one is a button, the current one is
|
|
32298
|
+
flagged with aria-current, which is what a screen reader reads out. -->
|
|
32299
|
+
<nav [attr.aria-label]="'settings.title' | transloco" class="flex flex-col gap-(--spacing-3xs)">
|
|
32300
|
+
@for (item of items; track item.id) {
|
|
32301
|
+
<button
|
|
32302
|
+
type="button"
|
|
32303
|
+
variant="none"
|
|
32304
|
+
class="flex h-(--row-height-md) w-full cursor-pointer items-center justify-start gap-(--spacing-xs) rounded-(--radius-sm) border-0 bg-transparent px-(--spacing-xs) text-left text-(--layer-page-font) transition-colors hover:bg-(--bg-primary-light) focus-visible:outline-2 focus-visible:outline-offset-0 focus-visible:outline-(--stroke-focus) aria-[current=page]:bg-(--bg-primary-base) aria-[current=page]:text-(--font-neutral-white) aria-[current=page]:hover:bg-(--bg-primary-base)"
|
|
32305
|
+
[attr.aria-current]="panel() === item.id ? 'page' : null"
|
|
32306
|
+
(click)="panel.set(item.id)">
|
|
32307
|
+
@switch (item.id) {
|
|
32308
|
+
@case ("account") {
|
|
32309
|
+
<user-icon aria-hidden="true" />
|
|
32310
|
+
}
|
|
32311
|
+
@case ("appearance") {
|
|
32312
|
+
<palette-icon aria-hidden="true" />
|
|
32313
|
+
}
|
|
32314
|
+
@case ("shortcuts") {
|
|
32315
|
+
<keyboard-icon aria-hidden="true" />
|
|
32316
|
+
}
|
|
32317
|
+
}
|
|
32318
|
+
{{ item.labelKey | transloco }}
|
|
32319
|
+
</button>
|
|
32320
|
+
}
|
|
32321
|
+
</nav>
|
|
32322
|
+
</div>
|
|
32323
|
+
|
|
32324
|
+
<div class="m-(--spacing-xs) flex min-w-0 flex-1 flex-col overflow-y-auto rounded-(--radius-lg) bg-(--layer-page-layer) p-(--spacing-xl)">
|
|
32325
|
+
@switch (panel()) {
|
|
32326
|
+
@case ("account") {
|
|
32327
|
+
<settings-account-panel class="flex min-h-0 flex-1 flex-col" />
|
|
32328
|
+
}
|
|
32329
|
+
@case ("appearance") {
|
|
32330
|
+
<settings-appearance-panel />
|
|
32331
|
+
}
|
|
32332
|
+
@case ("shortcuts") {
|
|
32333
|
+
<settings-shortcuts-panel />
|
|
32334
|
+
}
|
|
32335
|
+
}
|
|
32336
|
+
</div>
|
|
32337
|
+
</DialogContent>
|
|
32338
|
+
</div>
|
|
32339
|
+
}
|
|
31693
32340
|
`
|
|
31694
32341
|
}]
|
|
31695
32342
|
}],
|
|
@@ -31699,7 +32346,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
31699
32346
|
args: [i0.forwardRef(() => DialogComponent), { isSignal: true }]
|
|
31700
32347
|
}] }
|
|
31701
32348
|
});
|
|
31702
|
-
const
|
|
32349
|
+
const Settings = createCallable(SettingsDialogComponent);
|
|
31703
32350
|
const auditInterceptorFn = (request, next) => {
|
|
31704
32351
|
if (!request.url.includes("api/v1")) return next(request);
|
|
31705
32352
|
if (isJsonable(request.body) && !(request.body instanceof HttpParams)) addAuditAdditionalInfo(request.body);
|
|
@@ -32150,6 +32797,6 @@ function provideSessionExpiry(options = {}) {
|
|
|
32150
32797
|
const queryNameResolver = () => {
|
|
32151
32798
|
return inject(AppStore).getDefaultQuery()?.name || "";
|
|
32152
32799
|
};
|
|
32153
|
-
export { AGGREGATIONS_NAMES, AGGREGATIONS_NAMES_PRESET_DEFAULT, APP_FEATURES, ATOMIC_CLIENT, ATOMIC_SCOPED_PROVIDERS, AddToCollection, AdvancedFiltersBodyComponent, AdvancedFiltersComponent, AdvancedSearch, AdvancedSearchTabs, AggregationComponent, AggregationDateComponent, AggregationDateRange, AggregationItemDirective, AggregationListComponent, AggregationPanelComponent, AggregationTreeComponent, AggregationsService, AggregationsStore, Alert, AlertDialog, AlertForm, AlertsComponent, AppService, AppStore, ApplicationService, ApplicationStore, AppliedFiltersComponent, ArticleEntities, ArticleEntitiesTabPanel, ArticleExtracts, ArticleExtractsTabPanel, ArticleHighlights, ArticleLabels, ArticleLabelsTabPanel, ArticleMatchLocations, ArticleMatchLocationsTabPanel, ArticleSimilarDocuments, ArticleSimilarDocumentsTabPanel, AsideFiltersComponent, AuditFeedbackType, AuditService, AuthGuard, AuthPageComponent, AutocompleteService, BOOKMARKS_CONFIG, BOOKMARKS_OPTIONS, BackdropComponent, BackdropService, BookmarkButtonComponent, BookmarksComponent, COLLECTIONS_CONFIG, COLLECTIONS_OPTIONS, COMPONENTS_FOR_DOCUMENT_TYPE, ChangePasswordComponent, ChildMarkerDirective, CollectionsComponent, CollectionsDialog, ConverterSelectComponent, DeleteCollection, DeleteCollectionDialog, DidYouMeanComponent, DocumentLocatorComponent, EditLabels, ErrorComponent, Export, ExportDialog, ExportService, FILTERS_BREAKPOINT, FILTER_DATE_ALLOW_CUSTOM_RANGE, FORM_ALERT_CLASS, FORM_BARE_CLASS, FORM_ERROR_CLASS, FORM_FIELD_CLASS, FORM_FOOTER_CLASS, FORM_LABEL_CLASS, FORM_SURFACE_CLASS, FaIconComponent, FeatureFlags, FeatureFlagsDialogComponent, Feedback, FeedbackDialogComponent, FileSizePipe, FilterButtonComponent, FiltersBarComponent, FloatingToolbarComponent, ForgotPasswordComponent, HIGHLIGHTS, HeatmapComponent, HighlightWordPipe, InfinityScrollDirective, InlineWorker, JsonMethodPluginService, KeyboardShortcutsDialog, KeyboardShortcutsDialogComponent, KeyboardShortcutsService, LabelService, LabelsEditDialog, ListPanelItemDirective, LoadingComponent, MarkdownPipe, MarkdownRendererService, MetadataComponent, MissingTermsComponent, MoreButtonComponent, MoreComponent, MultiSelectLabelsComponent, MultiSelectionToolbarComponent, NON_SEARCHABLE_COLUMNS, NON_SEARCHABLE_DEFAULTS, NavbarTabsComponent, NavigationService, NoResultComponent, OpenArticleOnCtrlEnterDirective, OperatorPipe, OverflowItemDirective, OverflowManagerDirective, OverflowStopDirective, OverrideUser, OverrideUserDialogComponent, PREVIEW_ASSISTANT_COMPONENT, PREVIEW_CONFIG, PagerComponent, PreviewActionsComponent, PreviewContentComponent, PreviewHeaderComponent, PreviewNavigator, PreviewService, PrincipalService, PrincipalStore, QueryParamsStore, QueryService, RECENT_SEARCHES_CONFIG, RECENT_SEARCHES_OPTIONS, ROUTE_COMPONENTS, RecentSearchesComponent, ResetUserSettings, ResetUserSettingsDialogComponent, SAVED_SEARCHES_CONFIG, SAVED_SEARCHES_OPTIONS, SavedSearch, SavedSearchDialog, SavedSearchPopover, SavedSearchesComponent, SavedSearchesService, SearchFeedbackComponent, SearchInputFooter, SearchService, SelectArticleDirective, SelectionService, SelectionStore, ShortcutDirective, ShowBookmarkDirective, SignInComponent, SignedOutComponent, SortSelectorComponent, SourceComponent, SourceIconPipe, SponsoredResultsComponent, SyslangPipe, THEMES, TagCloudComponent, TextChunkService, ThemeProviderDirective, ThemeSelectorComponent, ThemeStore, ThemeToggleComponent, TranslocoDateImpurePipe, UserOverrideService,
|
|
32800
|
+
export { AGGREGATIONS_NAMES, AGGREGATIONS_NAMES_PRESET_DEFAULT, APP_FEATURES, ATOMIC_CLIENT, ATOMIC_SCOPED_PROVIDERS, AddToCollection, AdvancedFiltersBodyComponent, AdvancedFiltersComponent, AdvancedSearch, AdvancedSearchTabs, AggregationComponent, AggregationDateComponent, AggregationDateRange, AggregationItemDirective, AggregationListComponent, AggregationPanelComponent, AggregationTreeComponent, AggregationsService, AggregationsStore, Alert, AlertDialog, AlertForm, AlertsComponent, AppService, AppStore, ApplicationService, ApplicationStore, AppliedFiltersComponent, ArticleEntities, ArticleEntitiesTabPanel, ArticleExtracts, ArticleExtractsTabPanel, ArticleHighlights, ArticleLabels, ArticleLabelsTabPanel, ArticleMatchLocations, ArticleMatchLocationsTabPanel, ArticleSimilarDocuments, ArticleSimilarDocumentsTabPanel, AsideFiltersComponent, AuditFeedbackType, AuditService, AuthGuard, AuthPageComponent, AutocompleteService, BOOKMARKS_CONFIG, BOOKMARKS_OPTIONS, BackdropComponent, BackdropService, BookmarkButtonComponent, BookmarksComponent, COLLECTIONS_CONFIG, COLLECTIONS_OPTIONS, COMPONENTS_FOR_DOCUMENT_TYPE, ChangePasswordComponent, ChildMarkerDirective, CollectionsComponent, CollectionsDialog, ConverterSelectComponent, DeleteCollection, DeleteCollectionDialog, DidYouMeanComponent, DocumentLocatorComponent, EditLabels, ErrorComponent, Export, ExportDialog, ExportService, FILTERS_BREAKPOINT, FILTER_DATE_ALLOW_CUSTOM_RANGE, FORM_ALERT_CLASS, FORM_BARE_CLASS, FORM_ERROR_CLASS, FORM_FIELD_CLASS, FORM_FOOTER_CLASS, FORM_LABEL_CLASS, FORM_SURFACE_CLASS, FaIconComponent, FeatureFlags, FeatureFlagsDialogComponent, Feedback, FeedbackDialogComponent, FileSizePipe, FilterButtonComponent, FiltersBarComponent, FloatingToolbarComponent, ForgotPasswordComponent, HIGHLIGHTS, HeatmapComponent, HighlightWordPipe, InfinityScrollDirective, InlineWorker, JsonMethodPluginService, KeyboardShortcutsDialog, KeyboardShortcutsDialogComponent, KeyboardShortcutsListComponent, KeyboardShortcutsService, LabelService, LabelsEditDialog, ListPanelItemDirective, LoadingComponent, MarkdownPipe, MarkdownRendererService, MetadataComponent, MissingTermsComponent, MoreButtonComponent, MoreComponent, MultiSelectLabelsComponent, MultiSelectionToolbarComponent, NON_SEARCHABLE_COLUMNS, NON_SEARCHABLE_DEFAULTS, NavbarTabsComponent, NavigationService, NoResultComponent, OpenArticleOnCtrlEnterDirective, OperatorPipe, OverflowItemDirective, OverflowManagerDirective, OverflowStopDirective, OverrideUser, OverrideUserDialogComponent, PREVIEW_ASSISTANT_COMPONENT, PREVIEW_CONFIG, PagerComponent, PreviewActionsComponent, PreviewContentComponent, PreviewHeaderComponent, PreviewNavigator, PreviewService, PrincipalService, PrincipalStore, QueryParamsStore, QueryService, RECENT_SEARCHES_CONFIG, RECENT_SEARCHES_OPTIONS, ROUTE_COMPONENTS, RecentSearchesComponent, ResetUserSettings, ResetUserSettingsDialogComponent, SAVED_SEARCHES_CONFIG, SAVED_SEARCHES_OPTIONS, SavedSearch, SavedSearchDialog, SavedSearchPopover, SavedSearchesComponent, SavedSearchesService, SearchFeedbackComponent, SearchInputFooter, SearchService, SelectArticleDirective, SelectionService, SelectionStore, Settings, SettingsAccountPanelComponent, SettingsAppearancePanelComponent, SettingsDialogComponent, SettingsShortcutsPanelComponent, ShortcutDirective, ShowBookmarkDirective, SignInComponent, SignedOutComponent, SortSelectorComponent, SourceComponent, SourceIconPipe, SponsoredResultsComponent, SyslangPipe, THEMES, TagCloudComponent, TextChunkService, ThemeProviderDirective, ThemeSelectorComponent, ThemeStore, ThemeToggleComponent, TranslocoDateImpurePipe, UserOverrideService, UserProfileFormComponent, UserProfileService, UserSettingsPersistence, UserSettingsStore, WEIGHT_STEPS, WordCloudComponent, ZoomControlsComponent, __resetSignInMutexForTests, activateShortcutTarget, applyThemeToNativeElement, atomicClientProvider, auditInterceptorFn, authInterceptorFn, bodyInterceptorFn, bootstrapApp, buildHeatmap, buildQuery, buildTagCloud, candidatesFor, canonicalize, debouncedSignal, errorInterceptorFn, fieldErrorKey, fieldShowsError, filterKey, filterLeaves, filterValues, findAggregation, flagsFromWeekdays, focusWhenReady, galactikPrimitive, galactikSemantic, getCurrentPath, getCurrentQueryName, getQueryNameFromRoute, injectAdvancedFilters, injectAtomicClient, injectRouteNavigation, injectUserSettingsPatch, isAuthRoute, isSignInInFlight, itemLabel, layoutWordCloud, legacyFilterToFilter, makeWeightScale, multiSelectionToolbarVariants, paletteColor, processCssVars, provideAtomicClient, provideAtomicNotifications, provideAtomicScope, provideDocumentLang, provideKeyboardShortcuts, provideSessionExpiry, quantileScale, queryNameResolver, rebuildWithout, seriesPalette, signIn, themeColorNameToCssVariable, themeColorsToCssVariables, timelinePalette, toAriaKeyshortcuts, toDisplayParts, toPercentages, toSeries, toastInterceptorFn, weekdaysFromFlags, withAggregationsFeatures, withAlertsFeatures, withAlpha, withAppFeatures, withApplicationFeatures, withAssistantFeatures, withBasketsFeatures, withBookmarkFeatures, withBootstrapApp, withExtractsFeatures, withFetch, withMultiSelectionFeatures, withQueryParamsFeatures, withRecentSearchesFeatures, withSavedSearchesFeatures, withSelectionFeatures, withThemeBodyHook, withThemes, withThemesFeatures, withUserSettingsFeatures };
|
|
32154
32801
|
|
|
32155
32802
|
//# sourceMappingURL=sinequa-atomic-angular.mjs.map
|