@sinequa/atomic-angular 2.2.0 → 2.4.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/README.md +299 -139
- 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 +1003 -282
- package/fesm2022/sinequa-atomic-angular.mjs.map +1 -1
- package/package.json +6 -2
- package/types/sinequa-atomic-angular-document-workspaces.d.ts +9 -9
- package/types/sinequa-atomic-angular.d.ts +378 -148
|
@@ -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";
|
|
@@ -3354,7 +3354,8 @@ const QUERY_PARAMS_DEFAULTS = {
|
|
|
3354
3354
|
aggregations: void 0,
|
|
3355
3355
|
orderBy: "",
|
|
3356
3356
|
groupBy: "",
|
|
3357
|
-
neuralSearch: void 0
|
|
3357
|
+
neuralSearch: void 0,
|
|
3358
|
+
aggregation_overrides: void 0
|
|
3358
3359
|
};
|
|
3359
3360
|
const QueryParamsStore = signalStore({ providedIn: "root" }, withDevtools("QueryParams"), withQueryParamsFeatures());
|
|
3360
3361
|
function withQueryParamsFeatures() {
|
|
@@ -3578,7 +3579,7 @@ function withQueryParamsFeatures() {
|
|
|
3578
3579
|
};
|
|
3579
3580
|
},
|
|
3580
3581
|
getQuery() {
|
|
3581
|
-
const { filters: allFilters = [], sort, tab, basket, text: queryText = "", spellingCorrectionMode } = getState(store);
|
|
3582
|
+
const { filters: allFilters = [], sort, tab, basket, text: queryText = "", spellingCorrectionMode, aggregation_overrides } = getState(store);
|
|
3582
3583
|
let { name } = getState(store);
|
|
3583
3584
|
if (!name) name = (route.snapshot.firstChild?.children[0]?.data)?.["queryName"] || appStore.getDefaultQuery()?.name || "";
|
|
3584
3585
|
let text = queryText;
|
|
@@ -3599,7 +3600,8 @@ function withQueryParamsFeatures() {
|
|
|
3599
3600
|
spellingCorrectionMode,
|
|
3600
3601
|
tab,
|
|
3601
3602
|
text,
|
|
3602
|
-
basket
|
|
3603
|
+
basket,
|
|
3604
|
+
aggregation_overrides
|
|
3603
3605
|
}));
|
|
3604
3606
|
}
|
|
3605
3607
|
})));
|
|
@@ -20658,9 +20660,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
20658
20660
|
}] }
|
|
20659
20661
|
});
|
|
20660
20662
|
const FeatureFlags = createCallable(FeatureFlagsDialogComponent);
|
|
20661
|
-
var
|
|
20662
|
-
call = injectCallRef();
|
|
20663
|
-
dialog = viewChild(DialogComponent, ...ngDevMode ? [{ debugName: "dialog" }] : /* istanbul ignore next */ []);
|
|
20663
|
+
var KeyboardShortcutsListComponent = class KeyboardShortcutsListComponent {
|
|
20664
20664
|
keyboard = inject(KeyboardShortcutsService);
|
|
20665
20665
|
groups = computed(() => {
|
|
20666
20666
|
const listed = this.keyboard.shortcuts().filter((shortcut) => !shortcut.hidden && shortcut.labelKey);
|
|
@@ -20675,6 +20675,146 @@ var KeyboardShortcutsDialogComponent = class KeyboardShortcutsDialogComponent {
|
|
|
20675
20675
|
items
|
|
20676
20676
|
}));
|
|
20677
20677
|
}, ...ngDevMode ? [{ debugName: "groups" }] : /* istanbul ignore next */ []);
|
|
20678
|
+
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
20679
|
+
minVersion: "12.0.0",
|
|
20680
|
+
version: "22.2.1",
|
|
20681
|
+
ngImport: i0,
|
|
20682
|
+
type: KeyboardShortcutsListComponent,
|
|
20683
|
+
deps: [],
|
|
20684
|
+
target: i0.ɵɵFactoryTarget.Component
|
|
20685
|
+
});
|
|
20686
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({
|
|
20687
|
+
minVersion: "17.0.0",
|
|
20688
|
+
version: "22.2.1",
|
|
20689
|
+
type: KeyboardShortcutsListComponent,
|
|
20690
|
+
isStandalone: true,
|
|
20691
|
+
selector: "keyboard-shortcuts-list",
|
|
20692
|
+
providers: [provideTranslocoScope("dialogs")],
|
|
20693
|
+
ngImport: i0,
|
|
20694
|
+
template: `
|
|
20695
|
+
@if (groups().length === 0) {
|
|
20696
|
+
<p>{{ "dialogs.keyboardShortcuts.empty" | transloco }}</p>
|
|
20697
|
+
}
|
|
20698
|
+
|
|
20699
|
+
@for (group of groups(); track group.titleKey ?? "") {
|
|
20700
|
+
@if (group.titleKey) {
|
|
20701
|
+
<h3 class="mt-(--spacing-md) mb-(--spacing-4xs) text-(length:--size-lg) font-semibold first:mt-0">{{ group.titleKey | transloco }}</h3>
|
|
20702
|
+
}
|
|
20703
|
+
<dl>
|
|
20704
|
+
@for (shortcut of group.items; track shortcut) {
|
|
20705
|
+
<div class="flex items-center justify-between gap-(--spacing-md) py-(--spacing-4xs)">
|
|
20706
|
+
<dt>{{ shortcut.labelKey! | transloco }}</dt>
|
|
20707
|
+
<dd class="flex items-center gap-(--spacing-3xs)">
|
|
20708
|
+
<!-- One group per combination: a shortcut may answer to several (Alt+0 and
|
|
20709
|
+
Shift+?). The group carries the whole combination as its name and the keys
|
|
20710
|
+
are hidden, otherwise a screen reader reads out every key and separator as
|
|
20711
|
+
a separate item. -->
|
|
20712
|
+
<!-- comboIndex is captured explicitly: inside the nested loop $index would
|
|
20713
|
+
refer to the keys, not to the combination.
|
|
20714
|
+
(No backticks in this comment: the template is a template literal.) -->
|
|
20715
|
+
@for (aria of shortcut.aria; track aria; let first = $first, comboIndex = $index) {
|
|
20716
|
+
@if (!first) {
|
|
20717
|
+
<span aria-hidden="true">{{ "dialogs.keyboardShortcuts.or" | transloco }}</span>
|
|
20718
|
+
}
|
|
20719
|
+
<KbdGroup [attr.aria-label]="aria">
|
|
20720
|
+
@for (part of shortcut.displayParts[comboIndex]; track $index; let firstPart = $first) {
|
|
20721
|
+
@if (!firstPart) {
|
|
20722
|
+
<span aria-hidden="true">+</span>
|
|
20723
|
+
}
|
|
20724
|
+
<kbd aria-hidden="true">{{ part }}</kbd>
|
|
20725
|
+
}
|
|
20726
|
+
</KbdGroup>
|
|
20727
|
+
}
|
|
20728
|
+
</dd>
|
|
20729
|
+
</div>
|
|
20730
|
+
}
|
|
20731
|
+
</dl>
|
|
20732
|
+
}
|
|
20733
|
+
`,
|
|
20734
|
+
isInline: true,
|
|
20735
|
+
dependencies: [
|
|
20736
|
+
{
|
|
20737
|
+
kind: "directive",
|
|
20738
|
+
type: KbdComponent,
|
|
20739
|
+
selector: "kbd, Kbd",
|
|
20740
|
+
inputs: ["class", "size"]
|
|
20741
|
+
},
|
|
20742
|
+
{
|
|
20743
|
+
kind: "directive",
|
|
20744
|
+
type: KbdGroupComponent,
|
|
20745
|
+
selector: "kbd-group, kbdgroup, KbdGroup"
|
|
20746
|
+
},
|
|
20747
|
+
{
|
|
20748
|
+
kind: "pipe",
|
|
20749
|
+
type: TranslocoPipe,
|
|
20750
|
+
name: "transloco"
|
|
20751
|
+
}
|
|
20752
|
+
],
|
|
20753
|
+
changeDetection: i0.ChangeDetectionStrategy.OnPush
|
|
20754
|
+
});
|
|
20755
|
+
};
|
|
20756
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
20757
|
+
minVersion: "12.0.0",
|
|
20758
|
+
version: "22.2.1",
|
|
20759
|
+
ngImport: i0,
|
|
20760
|
+
type: KeyboardShortcutsListComponent,
|
|
20761
|
+
decorators: [{
|
|
20762
|
+
type: Component,
|
|
20763
|
+
args: [{
|
|
20764
|
+
selector: "keyboard-shortcuts-list",
|
|
20765
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
20766
|
+
imports: [
|
|
20767
|
+
TranslocoPipe,
|
|
20768
|
+
KbdComponent,
|
|
20769
|
+
KbdGroupComponent
|
|
20770
|
+
],
|
|
20771
|
+
providers: [provideTranslocoScope("dialogs")],
|
|
20772
|
+
template: `
|
|
20773
|
+
@if (groups().length === 0) {
|
|
20774
|
+
<p>{{ "dialogs.keyboardShortcuts.empty" | transloco }}</p>
|
|
20775
|
+
}
|
|
20776
|
+
|
|
20777
|
+
@for (group of groups(); track group.titleKey ?? "") {
|
|
20778
|
+
@if (group.titleKey) {
|
|
20779
|
+
<h3 class="mt-(--spacing-md) mb-(--spacing-4xs) text-(length:--size-lg) font-semibold first:mt-0">{{ group.titleKey | transloco }}</h3>
|
|
20780
|
+
}
|
|
20781
|
+
<dl>
|
|
20782
|
+
@for (shortcut of group.items; track shortcut) {
|
|
20783
|
+
<div class="flex items-center justify-between gap-(--spacing-md) py-(--spacing-4xs)">
|
|
20784
|
+
<dt>{{ shortcut.labelKey! | transloco }}</dt>
|
|
20785
|
+
<dd class="flex items-center gap-(--spacing-3xs)">
|
|
20786
|
+
<!-- One group per combination: a shortcut may answer to several (Alt+0 and
|
|
20787
|
+
Shift+?). The group carries the whole combination as its name and the keys
|
|
20788
|
+
are hidden, otherwise a screen reader reads out every key and separator as
|
|
20789
|
+
a separate item. -->
|
|
20790
|
+
<!-- comboIndex is captured explicitly: inside the nested loop $index would
|
|
20791
|
+
refer to the keys, not to the combination.
|
|
20792
|
+
(No backticks in this comment: the template is a template literal.) -->
|
|
20793
|
+
@for (aria of shortcut.aria; track aria; let first = $first, comboIndex = $index) {
|
|
20794
|
+
@if (!first) {
|
|
20795
|
+
<span aria-hidden="true">{{ "dialogs.keyboardShortcuts.or" | transloco }}</span>
|
|
20796
|
+
}
|
|
20797
|
+
<KbdGroup [attr.aria-label]="aria">
|
|
20798
|
+
@for (part of shortcut.displayParts[comboIndex]; track $index; let firstPart = $first) {
|
|
20799
|
+
@if (!firstPart) {
|
|
20800
|
+
<span aria-hidden="true">+</span>
|
|
20801
|
+
}
|
|
20802
|
+
<kbd aria-hidden="true">{{ part }}</kbd>
|
|
20803
|
+
}
|
|
20804
|
+
</KbdGroup>
|
|
20805
|
+
}
|
|
20806
|
+
</dd>
|
|
20807
|
+
</div>
|
|
20808
|
+
}
|
|
20809
|
+
</dl>
|
|
20810
|
+
}
|
|
20811
|
+
`
|
|
20812
|
+
}]
|
|
20813
|
+
}]
|
|
20814
|
+
});
|
|
20815
|
+
var KeyboardShortcutsDialogComponent = class KeyboardShortcutsDialogComponent {
|
|
20816
|
+
call = injectCallRef();
|
|
20817
|
+
dialog = viewChild(DialogComponent, ...ngDevMode ? [{ debugName: "dialog" }] : /* istanbul ignore next */ []);
|
|
20678
20818
|
constructor() {
|
|
20679
20819
|
afterNextRender(() => this.dialog()?.showModal());
|
|
20680
20820
|
}
|
|
@@ -20687,7 +20827,7 @@ var KeyboardShortcutsDialogComponent = class KeyboardShortcutsDialogComponent {
|
|
|
20687
20827
|
target: i0.ɵɵFactoryTarget.Component
|
|
20688
20828
|
});
|
|
20689
20829
|
static ɵcmp = i0.ɵɵngDeclareComponent({
|
|
20690
|
-
minVersion: "17.
|
|
20830
|
+
minVersion: "17.2.0",
|
|
20691
20831
|
version: "22.2.1",
|
|
20692
20832
|
type: KeyboardShortcutsDialogComponent,
|
|
20693
20833
|
isStandalone: true,
|
|
@@ -20709,44 +20849,7 @@ var KeyboardShortcutsDialogComponent = class KeyboardShortcutsDialogComponent {
|
|
|
20709
20849
|
</DialogHeader>
|
|
20710
20850
|
|
|
20711
20851
|
<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
|
-
}
|
|
20852
|
+
<keyboard-shortcuts-list />
|
|
20750
20853
|
</DialogBody>
|
|
20751
20854
|
|
|
20752
20855
|
<DialogFooter>
|
|
@@ -20812,15 +20915,9 @@ var KeyboardShortcutsDialogComponent = class KeyboardShortcutsDialogComponent {
|
|
|
20812
20915
|
inputs: ["class"]
|
|
20813
20916
|
},
|
|
20814
20917
|
{
|
|
20815
|
-
kind: "
|
|
20816
|
-
type:
|
|
20817
|
-
selector: "
|
|
20818
|
-
inputs: ["class", "size"]
|
|
20819
|
-
},
|
|
20820
|
-
{
|
|
20821
|
-
kind: "directive",
|
|
20822
|
-
type: KbdGroupComponent,
|
|
20823
|
-
selector: "kbd-group, kbdgroup, KbdGroup"
|
|
20918
|
+
kind: "component",
|
|
20919
|
+
type: KeyboardShortcutsListComponent,
|
|
20920
|
+
selector: "keyboard-shortcuts-list"
|
|
20824
20921
|
},
|
|
20825
20922
|
{
|
|
20826
20923
|
kind: "pipe",
|
|
@@ -20850,8 +20947,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
20850
20947
|
DialogTitleComponent,
|
|
20851
20948
|
DialogBodyComponent,
|
|
20852
20949
|
DialogFooterComponent,
|
|
20853
|
-
|
|
20854
|
-
KbdGroupComponent
|
|
20950
|
+
KeyboardShortcutsListComponent
|
|
20855
20951
|
],
|
|
20856
20952
|
providers: [provideTranslocoScope("dialogs")],
|
|
20857
20953
|
template: `
|
|
@@ -20862,44 +20958,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
20862
20958
|
</DialogHeader>
|
|
20863
20959
|
|
|
20864
20960
|
<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
|
-
}
|
|
20961
|
+
<keyboard-shortcuts-list />
|
|
20903
20962
|
</DialogBody>
|
|
20904
20963
|
|
|
20905
20964
|
<DialogFooter>
|
|
@@ -22792,6 +22851,7 @@ var AggregationDateComponent = class AggregationDateComponent {
|
|
|
22792
22851
|
onClear = output();
|
|
22793
22852
|
selectionChange = output();
|
|
22794
22853
|
filtersChange = output();
|
|
22854
|
+
aggregationOverridesChange = output();
|
|
22795
22855
|
isCollapsed = linkedSignal(() => this.collapsed(), ...ngDevMode ? [{ debugName: "isCollapsed" }] : /* istanbul ignore next */ []);
|
|
22796
22856
|
expanded = computed(() => this.isCollapsed() ? null : "", ...ngDevMode ? [{ debugName: "expanded" }] : /* istanbul ignore next */ []);
|
|
22797
22857
|
searchText = model("", ...ngDevMode ? [{ debugName: "searchText" }] : /* istanbul ignore next */ []);
|
|
@@ -22915,15 +22975,58 @@ var AggregationDateComponent = class AggregationDateComponent {
|
|
|
22915
22975
|
kind: "option",
|
|
22916
22976
|
option: this.optionToItemValue(option) ?? option
|
|
22917
22977
|
} : this.toRangeSelection(filter));
|
|
22978
|
+
this.aggregationOverridesChange.emit(this.overridesWithMask(this.selectedMask()));
|
|
22918
22979
|
this.filtersChange.emit(legacyFilterToFilter(filter));
|
|
22919
22980
|
}
|
|
22981
|
+
selectedMask() {
|
|
22982
|
+
const option = this.dateModel().option;
|
|
22983
|
+
if (!option || option === "custom-range") return void 0;
|
|
22984
|
+
return this.dateOptions().find((o) => o.display === option)?.mask;
|
|
22985
|
+
}
|
|
22986
|
+
currentOverrides() {
|
|
22987
|
+
return this.isControlled() ? this.controlledQuery()?.aggregation_overrides : this.queryParamsStore.aggregation_overrides?.();
|
|
22988
|
+
}
|
|
22989
|
+
overridesWithMask(mask) {
|
|
22990
|
+
const key = this.aggregation()?.name ?? this.name();
|
|
22991
|
+
const overrides = { ...this.currentOverrides() };
|
|
22992
|
+
if (key) {
|
|
22993
|
+
if (mask) overrides[key] = { mask };
|
|
22994
|
+
else delete overrides[key];
|
|
22995
|
+
}
|
|
22996
|
+
return Object.keys(overrides).length > 0 ? overrides : void 0;
|
|
22997
|
+
}
|
|
22998
|
+
storeMask(mask) {
|
|
22999
|
+
const next = this.overridesWithMask(mask);
|
|
23000
|
+
if (JSON.stringify(next) === JSON.stringify(this.currentOverrides())) return;
|
|
23001
|
+
this.queryParamsStore.patch({ aggregation_overrides: next }, void 0, { syncUrl: false });
|
|
23002
|
+
}
|
|
23003
|
+
maskedLabel(option) {
|
|
23004
|
+
if (!option.mask || !option.value || option.display !== option.value) return void 0;
|
|
23005
|
+
const [year, month] = option.value.split("-").map(Number);
|
|
23006
|
+
const format = option.mask === "YYYY" ? { year: "numeric" } : {
|
|
23007
|
+
year: "numeric",
|
|
23008
|
+
month: "long"
|
|
23009
|
+
};
|
|
23010
|
+
try {
|
|
23011
|
+
return new Intl.DateTimeFormat(this.lang(), {
|
|
23012
|
+
...format,
|
|
23013
|
+
timeZone: "UTC"
|
|
23014
|
+
}).format(new Date(Date.UTC(year, (month ?? 1) - 1, 1)));
|
|
23015
|
+
} catch {
|
|
23016
|
+
return;
|
|
23017
|
+
}
|
|
23018
|
+
}
|
|
22920
23019
|
select() {
|
|
22921
23020
|
this.selection.set(true);
|
|
22922
23021
|
}
|
|
22923
23022
|
apply() {
|
|
22924
23023
|
try {
|
|
22925
23024
|
if (this.isControlled()) this.emitCurrentSelection();
|
|
22926
|
-
else
|
|
23025
|
+
else {
|
|
23026
|
+
const filter = this.getFormValueFilter();
|
|
23027
|
+
this.storeMask(this.selectedMask());
|
|
23028
|
+
this.queryParamsStore.updateFilter(filter, void 0, { syncUrl: this.syncUrl() });
|
|
23029
|
+
}
|
|
22927
23030
|
this.selection.set(false);
|
|
22928
23031
|
this.onApply.emit();
|
|
22929
23032
|
} catch (err) {
|
|
@@ -22943,8 +23046,12 @@ var AggregationDateComponent = class AggregationDateComponent {
|
|
|
22943
23046
|
if (agg && notify) {
|
|
22944
23047
|
if (this.isControlled()) {
|
|
22945
23048
|
this.selectionChange.emit({ kind: "range" });
|
|
23049
|
+
this.aggregationOverridesChange.emit(this.overridesWithMask(void 0));
|
|
22946
23050
|
this.filtersChange.emit(void 0);
|
|
22947
|
-
} else
|
|
23051
|
+
} else {
|
|
23052
|
+
this.storeMask(void 0);
|
|
23053
|
+
this.queryParamsStore.removeFilterByName(agg.name, void 0, { syncUrl: this.syncUrl() });
|
|
23054
|
+
}
|
|
22948
23055
|
this.onClear.emit();
|
|
22949
23056
|
}
|
|
22950
23057
|
}
|
|
@@ -23003,8 +23110,8 @@ var AggregationDateComponent = class AggregationDateComponent {
|
|
|
23003
23110
|
this.clear(false);
|
|
23004
23111
|
return;
|
|
23005
23112
|
}
|
|
23006
|
-
const { operator
|
|
23007
|
-
const code = this.
|
|
23113
|
+
const { operator } = filter;
|
|
23114
|
+
const code = this.findPresetCode(filter) ?? "custom-range";
|
|
23008
23115
|
let from = null, to = null;
|
|
23009
23116
|
if (code === "custom-range") switch (operator) {
|
|
23010
23117
|
case "lte":
|
|
@@ -23017,12 +23124,7 @@ var AggregationDateComponent = class AggregationDateComponent {
|
|
|
23017
23124
|
from = filter.start;
|
|
23018
23125
|
to = filter.end;
|
|
23019
23126
|
break;
|
|
23020
|
-
case "and": {
|
|
23021
|
-
const filters = filter.filters;
|
|
23022
|
-
from = filters?.find((f) => f.operator === "gte")?.value ?? null;
|
|
23023
|
-
to = filters?.find((f) => f.operator === "lte")?.value ?? null;
|
|
23024
|
-
break;
|
|
23025
|
-
}
|
|
23127
|
+
case "and": ({from, to} = this.toBounds(filter.filters));
|
|
23026
23128
|
}
|
|
23027
23129
|
const formValue = {
|
|
23028
23130
|
option: code,
|
|
@@ -23033,6 +23135,29 @@ var AggregationDateComponent = class AggregationDateComponent {
|
|
|
23033
23135
|
};
|
|
23034
23136
|
this.setModel(formValue);
|
|
23035
23137
|
}
|
|
23138
|
+
findPresetCode(filter) {
|
|
23139
|
+
const options = this.dateOptions();
|
|
23140
|
+
const exact = options.find((option) => option.operator === filter.operator && option.value === filter.value);
|
|
23141
|
+
if (exact) return exact.display;
|
|
23142
|
+
if (filter.operator !== "and") return void 0;
|
|
23143
|
+
const { from, to } = this.toBounds(filter.filters);
|
|
23144
|
+
if (!from || !to) return void 0;
|
|
23145
|
+
return options.find((option) => {
|
|
23146
|
+
if (option.operator !== "and") return false;
|
|
23147
|
+
const bounds = this.toBounds(option.filters);
|
|
23148
|
+
return bounds.from === from && bounds.to === to;
|
|
23149
|
+
})?.display;
|
|
23150
|
+
}
|
|
23151
|
+
toBounds(filters) {
|
|
23152
|
+
const bound = (...operators) => {
|
|
23153
|
+
const value = filters?.find((f) => operators.includes(f.operator))?.value;
|
|
23154
|
+
return value === void 0 || value === null ? null : String(value);
|
|
23155
|
+
};
|
|
23156
|
+
return {
|
|
23157
|
+
from: bound("gte"),
|
|
23158
|
+
to: bound("lte", "lt")
|
|
23159
|
+
};
|
|
23160
|
+
}
|
|
23036
23161
|
updateFormFromSelection(sel) {
|
|
23037
23162
|
if (sel?.kind === "option") {
|
|
23038
23163
|
const code = this.itemValueToOption(sel.option);
|
|
@@ -23094,11 +23219,11 @@ var AggregationDateComponent = class AggregationDateComponent {
|
|
|
23094
23219
|
to: filter.end
|
|
23095
23220
|
};
|
|
23096
23221
|
case "and": {
|
|
23097
|
-
const
|
|
23222
|
+
const { from, to } = this.toBounds(filter.filters);
|
|
23098
23223
|
return {
|
|
23099
23224
|
kind: "range",
|
|
23100
|
-
from:
|
|
23101
|
-
to:
|
|
23225
|
+
from: from ?? void 0,
|
|
23226
|
+
to: to ?? void 0
|
|
23102
23227
|
};
|
|
23103
23228
|
}
|
|
23104
23229
|
default: return {
|
|
@@ -23287,12 +23412,13 @@ var AggregationDateComponent = class AggregationDateComponent {
|
|
|
23287
23412
|
onClear: "onClear",
|
|
23288
23413
|
selectionChange: "selectionChange",
|
|
23289
23414
|
filtersChange: "filtersChange",
|
|
23415
|
+
aggregationOverridesChange: "aggregationOverridesChange",
|
|
23290
23416
|
searchText: "searchTextChange"
|
|
23291
23417
|
},
|
|
23292
23418
|
host: { properties: { "class": "cn(\"@container block\", isEmpty() && \"hidden\")" } },
|
|
23293
23419
|
providers: [provideTranslocoScope("filters")],
|
|
23294
23420
|
ngImport: i0,
|
|
23295
|
-
template: "<!-- `<details>`/`<summary>` stay native (open/close, the `name`-based exclusive accordion). A facet is a\n disclosure, not a grid row: see `aggregation-panel.html` for the why (same pattern, duplicated here\n since this facet type doesn't go through `AggregationPanelComponent`). The `<summary>` itself is\n `tabindex=\"-1\"`: the header's buttons (apply, clear, chevron) are the keyboard stops, see the\n chevron below. -->\n<details [attr.open]=\"expanded()\" [attr.name]=\"id()\" class=\"group space-y-2\">\n <!-- scroll-mb-3/scroll-mt-16: see aggregation-panel-row.html for the full rationale\n (bottom margin against the container's edge, much bigger top margin to also clear\n <More>'s sticky facet-name search box when navigating ArrowUp). -->\n <summary\n tabindex=\"-1\"\n [class.cursor-pointer]=\"collapsible()\"\n [class]=\"rowStateClass\"\n class=\"m-0 flex h-8 w-full scroll-mt-16 scroll-mb-3 items-center gap-1 pl-1 font-semibold select-none\"\n (click)=\"onHeaderClick($event)\">\n <ng-content select=\"label\">\n @let icon = aggregation()?.icon;\n @if (icon) {\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\n }\n <span class=\"grow\">{{\n aggregation()?.display | syslang | transloco\n }}</span>\n </ng-content>\n\n @if (hasFilters()) {\n @let clearLabel = \"filters.clearFilters\" | transloco;\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n size=\"sm\"\n class=\"shrink-0\"\n [aria-label]=\"clearLabel\"\n [tooltip]=\"clearLabel\"\n (click)=\"$event.stopPropagation(); clear()\">\n <filter-x-icon class=\"shrink-0\" />\n </button>\n }\n\n @if (\n (!isControlled() || emitOn() === \"apply\") &&\n selection() &&\n validSelection()\n ) {\n @let label = \"filters.apply\" | transloco;\n <button\n type=\"button\"\n variant=\"accent\"\n size=\"sm\"\n class=\"overflow-hidden\"\n [aria-label]=\"label\"\n [tooltip]=\"label\"\n (click)=\"$event.stopPropagation(); apply()\">\n <FilterIcon class=\"shrink-0\" />\n <span class=\"truncate\">{{ label }}</span>\n </button>\n }\n @if (collapsible()) {\n <!-- The `<summary>` is out of the tab order (`tabindex=\"-1\"`), so this button is the keyboard way to\n collapse the facet. Its click bubbles to the `<summary>`, whose handler does the toggle, and it\n exposes `aria-expanded`. -->\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n size=\"sm\"\n [aria-label]=\"'filters.toggle' | transloco\"\n [attr.aria-expanded]=\"!isCollapsed()\"\n [tooltip]=\"'filters.toggle' | transloco\"\n class=\"shrink-0 cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\n <chevron-right class=\"shrink-0\" />\n </button>\n }\n </summary>\n\n <!-- content wrapper -->\n <!-- RGAA 11.5 / WCAG 1.3.1: the radios below all share one `name`, so they are one set\n and have to be exposed as one. `role=\"group\"` here rather than `role=\"radiogroup\"`\n on the list: a radiogroup owns its radios directly, and the <li> in between would be\n invalid owned children — the list would also stop announcing how many options it\n holds. The name is the aggregation's own title, the words the header already shows;\n with no title the group stays unnamed rather than carrying an empty label. -->\n @let groupName = aggregation()?.display;\n <form (keydown.enter)=\"onOptionEnter($event)\">\n <div\n role=\"group\"\n [attr.aria-label]=\"groupName ? (groupName | syslang | transloco) : null\">\n <ul\n class=\"flex max-h-(--scroll-height,20rem) snap-y snap-start scrollbar-thin flex-col gap-0.5 overflow-auto pt-2\"\n role=\"list\">\n @for (option of dateOptions(); track
|
|
23421
|
+
template: "<!-- `<details>`/`<summary>` stay native (open/close, the `name`-based exclusive accordion). A facet is a\n disclosure, not a grid row: see `aggregation-panel.html` for the why (same pattern, duplicated here\n since this facet type doesn't go through `AggregationPanelComponent`). The `<summary>` itself is\n `tabindex=\"-1\"`: the header's buttons (apply, clear, chevron) are the keyboard stops, see the\n chevron below. -->\n<details [attr.open]=\"expanded()\" [attr.name]=\"id()\" class=\"group space-y-2\">\n <!-- scroll-mb-3/scroll-mt-16: see aggregation-panel-row.html for the full rationale\n (bottom margin against the container's edge, much bigger top margin to also clear\n <More>'s sticky facet-name search box when navigating ArrowUp). -->\n <summary\n tabindex=\"-1\"\n [class.cursor-pointer]=\"collapsible()\"\n [class]=\"rowStateClass\"\n class=\"m-0 flex h-8 w-full scroll-mt-16 scroll-mb-3 items-center gap-1 pl-1 font-semibold select-none\"\n (click)=\"onHeaderClick($event)\">\n <ng-content select=\"label\">\n @let icon = aggregation()?.icon;\n @if (icon) {\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\n }\n <span class=\"grow\">{{\n aggregation()?.display | syslang | transloco\n }}</span>\n </ng-content>\n\n @if (hasFilters()) {\n @let clearLabel = \"filters.clearFilters\" | transloco;\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n size=\"sm\"\n class=\"shrink-0\"\n [aria-label]=\"clearLabel\"\n [tooltip]=\"clearLabel\"\n (click)=\"$event.stopPropagation(); clear()\">\n <filter-x-icon class=\"shrink-0\" />\n </button>\n }\n\n @if (\n (!isControlled() || emitOn() === \"apply\") &&\n selection() &&\n validSelection()\n ) {\n @let label = \"filters.apply\" | transloco;\n <button\n type=\"button\"\n variant=\"accent\"\n size=\"sm\"\n class=\"overflow-hidden\"\n [aria-label]=\"label\"\n [tooltip]=\"label\"\n (click)=\"$event.stopPropagation(); apply()\">\n <FilterIcon class=\"shrink-0\" />\n <span class=\"truncate\">{{ label }}</span>\n </button>\n }\n @if (collapsible()) {\n <!-- The `<summary>` is out of the tab order (`tabindex=\"-1\"`), so this button is the keyboard way to\n collapse the facet. Its click bubbles to the `<summary>`, whose handler does the toggle, and it\n exposes `aria-expanded`. -->\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n size=\"sm\"\n [aria-label]=\"'filters.toggle' | transloco\"\n [attr.aria-expanded]=\"!isCollapsed()\"\n [tooltip]=\"'filters.toggle' | transloco\"\n class=\"shrink-0 cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\n <chevron-right class=\"shrink-0\" />\n </button>\n }\n </summary>\n\n <!-- content wrapper -->\n <!-- RGAA 11.5 / WCAG 1.3.1: the radios below all share one `name`, so they are one set\n and have to be exposed as one. `role=\"group\"` here rather than `role=\"radiogroup\"`\n on the list: a radiogroup owns its radios directly, and the <li> in between would be\n invalid owned children — the list would also stop announcing how many options it\n holds. The name is the aggregation's own title, the words the header already shows;\n with no title the group stays unnamed rather than carrying an empty label. -->\n @let groupName = aggregation()?.display;\n <form (keydown.enter)=\"onOptionEnter($event)\">\n <div\n role=\"group\"\n [attr.aria-label]=\"groupName ? (groupName | syslang | transloco) : null\">\n <ul\n class=\"flex max-h-(--scroll-height,20rem) snap-y snap-start scrollbar-thin flex-col gap-0.5 overflow-auto pt-2\"\n role=\"list\">\n @for (option of dateOptions(); track option.display) {\n <li\n role=\"listitem\"\n [attr.aria-label]=\"\n maskedLabel(option) ?? (option.display | syslang | transloco)\n \"\n [class]=\"\n cn(\n itemStateClass,\n 'flex cursor-pointer items-center p-2 text-sm text-(--font-neutral-base) select-none',\n dateModel().option === option.display &&\n 'bg-(--bg-primary-lighter)',\n option.hidden && 'hidden',\n option.disabled && 'pointer-events-none opacity-40'\n )\n \"\n [attr.aria-hidden]=\"option.disabled\">\n <radio\n name=\"date-filter\"\n class=\"grow\"\n [checked]=\"dateModel().option === option.display\"\n [disabled]=\"option.disabled\"\n (checkedChange)=\"selectOption(option.display)\">\n {{\n maskedLabel(option) ?? (option.display | syslang | transloco)\n }}\n </radio>\n </li>\n }\n\n @if (allowCustomRange) {\n <li\n role=\"listitem\"\n aria-label=\"open date range picker\"\n [class]=\"\n cn(\n itemStateClass,\n 'flex cursor-pointer items-center gap-(--spacing-xs) p-2 text-sm text-(--font-neutral-base) select-none',\n dateModel().option === 'custom-range' &&\n 'bg-(--bg-primary-lighter)'\n )\n \">\n <radio\n name=\"date-filter\"\n class=\"grow items-center\"\n [checked]=\"dateModel().option === 'custom-range'\"\n (checkedChange)=\"selectOption('custom-range')\">\n <div class=\"flex flex-wrap items-center gap-x-1 gap-y-1\">\n <div class=\"flex items-center gap-1\">\n <label\n [attr.for]=\"startId\"\n class=\"radio-label min-w-8 truncate\"\n >{{ \"filters.from\" | transloco }}</label\n >\n <input-group\n class=\"h-8 max-w-[18ch] min-w-[18ch] has-[[readonly]]:border-(--stroke-primary-muted)\">\n <input\n input-control\n [id]=\"startId\"\n name=\"start\"\n type=\"text\"\n readonly\n [value]=\"customRangeFrom()\"\n (click)=\"selectAndOpenDialog()\"\n (keydown.enter)=\"onRangeInputEnter($event)\" />\n </input-group>\n </div>\n <div class=\"flex items-center gap-1\">\n <label\n [attr.for]=\"endId\"\n class=\"radio-label min-w-8 truncate text-right\"\n >{{ \"filters.to\" | transloco }}</label\n >\n <input-group\n class=\"h-8 max-w-[18ch] min-w-[18ch] has-[[readonly]]:border-(--stroke-primary-muted)\">\n <input\n input-control\n [id]=\"endId\"\n name=\"end\"\n type=\"text\"\n readonly\n [value]=\"customRangeTo()\"\n (click)=\"selectAndOpenDialog()\"\n (keydown.enter)=\"onRangeInputEnter($event)\" />\n </input-group>\n </div>\n </div>\n </radio>\n </li>\n }\n </ul>\n </div>\n </form>\n</details>\n",
|
|
23296
23422
|
styles: [":host{min-width:200px}ul[role=list]{scrollbar-width:thin}\n"],
|
|
23297
23423
|
dependencies: [
|
|
23298
23424
|
{
|
|
@@ -23427,7 +23553,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
23427
23553
|
TooltipDirective
|
|
23428
23554
|
],
|
|
23429
23555
|
host: { "[class]": "cn(\"@container block\", isEmpty() && \"hidden\")" },
|
|
23430
|
-
template: "<!-- `<details>`/`<summary>` stay native (open/close, the `name`-based exclusive accordion). A facet is a\n disclosure, not a grid row: see `aggregation-panel.html` for the why (same pattern, duplicated here\n since this facet type doesn't go through `AggregationPanelComponent`). The `<summary>` itself is\n `tabindex=\"-1\"`: the header's buttons (apply, clear, chevron) are the keyboard stops, see the\n chevron below. -->\n<details [attr.open]=\"expanded()\" [attr.name]=\"id()\" class=\"group space-y-2\">\n <!-- scroll-mb-3/scroll-mt-16: see aggregation-panel-row.html for the full rationale\n (bottom margin against the container's edge, much bigger top margin to also clear\n <More>'s sticky facet-name search box when navigating ArrowUp). -->\n <summary\n tabindex=\"-1\"\n [class.cursor-pointer]=\"collapsible()\"\n [class]=\"rowStateClass\"\n class=\"m-0 flex h-8 w-full scroll-mt-16 scroll-mb-3 items-center gap-1 pl-1 font-semibold select-none\"\n (click)=\"onHeaderClick($event)\">\n <ng-content select=\"label\">\n @let icon = aggregation()?.icon;\n @if (icon) {\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\n }\n <span class=\"grow\">{{\n aggregation()?.display | syslang | transloco\n }}</span>\n </ng-content>\n\n @if (hasFilters()) {\n @let clearLabel = \"filters.clearFilters\" | transloco;\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n size=\"sm\"\n class=\"shrink-0\"\n [aria-label]=\"clearLabel\"\n [tooltip]=\"clearLabel\"\n (click)=\"$event.stopPropagation(); clear()\">\n <filter-x-icon class=\"shrink-0\" />\n </button>\n }\n\n @if (\n (!isControlled() || emitOn() === \"apply\") &&\n selection() &&\n validSelection()\n ) {\n @let label = \"filters.apply\" | transloco;\n <button\n type=\"button\"\n variant=\"accent\"\n size=\"sm\"\n class=\"overflow-hidden\"\n [aria-label]=\"label\"\n [tooltip]=\"label\"\n (click)=\"$event.stopPropagation(); apply()\">\n <FilterIcon class=\"shrink-0\" />\n <span class=\"truncate\">{{ label }}</span>\n </button>\n }\n @if (collapsible()) {\n <!-- The `<summary>` is out of the tab order (`tabindex=\"-1\"`), so this button is the keyboard way to\n collapse the facet. Its click bubbles to the `<summary>`, whose handler does the toggle, and it\n exposes `aria-expanded`. -->\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n size=\"sm\"\n [aria-label]=\"'filters.toggle' | transloco\"\n [attr.aria-expanded]=\"!isCollapsed()\"\n [tooltip]=\"'filters.toggle' | transloco\"\n class=\"shrink-0 cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\n <chevron-right class=\"shrink-0\" />\n </button>\n }\n </summary>\n\n <!-- content wrapper -->\n <!-- RGAA 11.5 / WCAG 1.3.1: the radios below all share one `name`, so they are one set\n and have to be exposed as one. `role=\"group\"` here rather than `role=\"radiogroup\"`\n on the list: a radiogroup owns its radios directly, and the <li> in between would be\n invalid owned children — the list would also stop announcing how many options it\n holds. The name is the aggregation's own title, the words the header already shows;\n with no title the group stays unnamed rather than carrying an empty label. -->\n @let groupName = aggregation()?.display;\n <form (keydown.enter)=\"onOptionEnter($event)\">\n <div\n role=\"group\"\n [attr.aria-label]=\"groupName ? (groupName | syslang | transloco) : null\">\n <ul\n class=\"flex max-h-(--scroll-height,20rem) snap-y snap-start scrollbar-thin flex-col gap-0.5 overflow-auto pt-2\"\n role=\"list\">\n @for (option of dateOptions(); track
|
|
23556
|
+
template: "<!-- `<details>`/`<summary>` stay native (open/close, the `name`-based exclusive accordion). A facet is a\n disclosure, not a grid row: see `aggregation-panel.html` for the why (same pattern, duplicated here\n since this facet type doesn't go through `AggregationPanelComponent`). The `<summary>` itself is\n `tabindex=\"-1\"`: the header's buttons (apply, clear, chevron) are the keyboard stops, see the\n chevron below. -->\n<details [attr.open]=\"expanded()\" [attr.name]=\"id()\" class=\"group space-y-2\">\n <!-- scroll-mb-3/scroll-mt-16: see aggregation-panel-row.html for the full rationale\n (bottom margin against the container's edge, much bigger top margin to also clear\n <More>'s sticky facet-name search box when navigating ArrowUp). -->\n <summary\n tabindex=\"-1\"\n [class.cursor-pointer]=\"collapsible()\"\n [class]=\"rowStateClass\"\n class=\"m-0 flex h-8 w-full scroll-mt-16 scroll-mb-3 items-center gap-1 pl-1 font-semibold select-none\"\n (click)=\"onHeaderClick($event)\">\n <ng-content select=\"label\">\n @let icon = aggregation()?.icon;\n @if (icon) {\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\n }\n <span class=\"grow\">{{\n aggregation()?.display | syslang | transloco\n }}</span>\n </ng-content>\n\n @if (hasFilters()) {\n @let clearLabel = \"filters.clearFilters\" | transloco;\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n size=\"sm\"\n class=\"shrink-0\"\n [aria-label]=\"clearLabel\"\n [tooltip]=\"clearLabel\"\n (click)=\"$event.stopPropagation(); clear()\">\n <filter-x-icon class=\"shrink-0\" />\n </button>\n }\n\n @if (\n (!isControlled() || emitOn() === \"apply\") &&\n selection() &&\n validSelection()\n ) {\n @let label = \"filters.apply\" | transloco;\n <button\n type=\"button\"\n variant=\"accent\"\n size=\"sm\"\n class=\"overflow-hidden\"\n [aria-label]=\"label\"\n [tooltip]=\"label\"\n (click)=\"$event.stopPropagation(); apply()\">\n <FilterIcon class=\"shrink-0\" />\n <span class=\"truncate\">{{ label }}</span>\n </button>\n }\n @if (collapsible()) {\n <!-- The `<summary>` is out of the tab order (`tabindex=\"-1\"`), so this button is the keyboard way to\n collapse the facet. Its click bubbles to the `<summary>`, whose handler does the toggle, and it\n exposes `aria-expanded`. -->\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n size=\"sm\"\n [aria-label]=\"'filters.toggle' | transloco\"\n [attr.aria-expanded]=\"!isCollapsed()\"\n [tooltip]=\"'filters.toggle' | transloco\"\n class=\"shrink-0 cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\n <chevron-right class=\"shrink-0\" />\n </button>\n }\n </summary>\n\n <!-- content wrapper -->\n <!-- RGAA 11.5 / WCAG 1.3.1: the radios below all share one `name`, so they are one set\n and have to be exposed as one. `role=\"group\"` here rather than `role=\"radiogroup\"`\n on the list: a radiogroup owns its radios directly, and the <li> in between would be\n invalid owned children — the list would also stop announcing how many options it\n holds. The name is the aggregation's own title, the words the header already shows;\n with no title the group stays unnamed rather than carrying an empty label. -->\n @let groupName = aggregation()?.display;\n <form (keydown.enter)=\"onOptionEnter($event)\">\n <div\n role=\"group\"\n [attr.aria-label]=\"groupName ? (groupName | syslang | transloco) : null\">\n <ul\n class=\"flex max-h-(--scroll-height,20rem) snap-y snap-start scrollbar-thin flex-col gap-0.5 overflow-auto pt-2\"\n role=\"list\">\n @for (option of dateOptions(); track option.display) {\n <li\n role=\"listitem\"\n [attr.aria-label]=\"\n maskedLabel(option) ?? (option.display | syslang | transloco)\n \"\n [class]=\"\n cn(\n itemStateClass,\n 'flex cursor-pointer items-center p-2 text-sm text-(--font-neutral-base) select-none',\n dateModel().option === option.display &&\n 'bg-(--bg-primary-lighter)',\n option.hidden && 'hidden',\n option.disabled && 'pointer-events-none opacity-40'\n )\n \"\n [attr.aria-hidden]=\"option.disabled\">\n <radio\n name=\"date-filter\"\n class=\"grow\"\n [checked]=\"dateModel().option === option.display\"\n [disabled]=\"option.disabled\"\n (checkedChange)=\"selectOption(option.display)\">\n {{\n maskedLabel(option) ?? (option.display | syslang | transloco)\n }}\n </radio>\n </li>\n }\n\n @if (allowCustomRange) {\n <li\n role=\"listitem\"\n aria-label=\"open date range picker\"\n [class]=\"\n cn(\n itemStateClass,\n 'flex cursor-pointer items-center gap-(--spacing-xs) p-2 text-sm text-(--font-neutral-base) select-none',\n dateModel().option === 'custom-range' &&\n 'bg-(--bg-primary-lighter)'\n )\n \">\n <radio\n name=\"date-filter\"\n class=\"grow items-center\"\n [checked]=\"dateModel().option === 'custom-range'\"\n (checkedChange)=\"selectOption('custom-range')\">\n <div class=\"flex flex-wrap items-center gap-x-1 gap-y-1\">\n <div class=\"flex items-center gap-1\">\n <label\n [attr.for]=\"startId\"\n class=\"radio-label min-w-8 truncate\"\n >{{ \"filters.from\" | transloco }}</label\n >\n <input-group\n class=\"h-8 max-w-[18ch] min-w-[18ch] has-[[readonly]]:border-(--stroke-primary-muted)\">\n <input\n input-control\n [id]=\"startId\"\n name=\"start\"\n type=\"text\"\n readonly\n [value]=\"customRangeFrom()\"\n (click)=\"selectAndOpenDialog()\"\n (keydown.enter)=\"onRangeInputEnter($event)\" />\n </input-group>\n </div>\n <div class=\"flex items-center gap-1\">\n <label\n [attr.for]=\"endId\"\n class=\"radio-label min-w-8 truncate text-right\"\n >{{ \"filters.to\" | transloco }}</label\n >\n <input-group\n class=\"h-8 max-w-[18ch] min-w-[18ch] has-[[readonly]]:border-(--stroke-primary-muted)\">\n <input\n input-control\n [id]=\"endId\"\n name=\"end\"\n type=\"text\"\n readonly\n [value]=\"customRangeTo()\"\n (click)=\"selectAndOpenDialog()\"\n (keydown.enter)=\"onRangeInputEnter($event)\" />\n </input-group>\n </div>\n </div>\n </radio>\n </li>\n }\n </ul>\n </div>\n </form>\n</details>\n",
|
|
23431
23557
|
styles: [":host{min-width:200px}ul[role=list]{scrollbar-width:thin}\n"]
|
|
23432
23558
|
}]
|
|
23433
23559
|
}],
|
|
@@ -23565,6 +23691,10 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
23565
23691
|
type: i0.Output,
|
|
23566
23692
|
args: ["filtersChange"]
|
|
23567
23693
|
}],
|
|
23694
|
+
aggregationOverridesChange: [{
|
|
23695
|
+
type: i0.Output,
|
|
23696
|
+
args: ["aggregationOverridesChange"]
|
|
23697
|
+
}],
|
|
23568
23698
|
searchText: [{
|
|
23569
23699
|
type: i0.Input,
|
|
23570
23700
|
args: [{
|
|
@@ -26417,6 +26547,7 @@ var AggregationComponent = class AggregationComponent {
|
|
|
26417
26547
|
"onClear",
|
|
26418
26548
|
"selectionChange",
|
|
26419
26549
|
"filtersChange",
|
|
26550
|
+
"aggregationOverridesChange",
|
|
26420
26551
|
"searchTextChange"
|
|
26421
26552
|
]
|
|
26422
26553
|
}
|
|
@@ -31045,11 +31176,6 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
31045
31176
|
}]
|
|
31046
31177
|
}] }
|
|
31047
31178
|
});
|
|
31048
|
-
const SUPPORTED_LANGUAGES = [
|
|
31049
|
-
"en",
|
|
31050
|
-
"fr",
|
|
31051
|
-
"de"
|
|
31052
|
-
];
|
|
31053
31179
|
const sameKeys = (a, b) => a.length === b.length && a.every((key, index) => key === b[index]);
|
|
31054
31180
|
const userProfileSchema = (editing) => schema((p) => {
|
|
31055
31181
|
applyEach(p.data, (field) => readonly(field, { when: ({ key }) => `data.${key()}` !== editing() }));
|
|
@@ -31057,7 +31183,6 @@ const userProfileSchema = (editing) => schema((p) => {
|
|
|
31057
31183
|
});
|
|
31058
31184
|
var UserProfileFormComponent = class UserProfileFormComponent {
|
|
31059
31185
|
principalStore = inject(PrincipalStore);
|
|
31060
|
-
userSettingsStore = inject(UserSettingsStore);
|
|
31061
31186
|
userProfileService = inject(UserProfileService);
|
|
31062
31187
|
appStore = inject(AppStore);
|
|
31063
31188
|
client = inject(ATOMIC_CLIENT);
|
|
@@ -31105,26 +31230,6 @@ var UserProfileFormComponent = class UserProfileFormComponent {
|
|
|
31105
31230
|
initials = computed(() => this.principalStore.initials(), ...ngDevMode ? [{ debugName: "initials" }] : /* istanbul ignore next */ []);
|
|
31106
31231
|
username = computed(() => this.principalStore.name() || this.principalStore.email() || null, ...ngDevMode ? [{ debugName: "username" }] : /* istanbul ignore next */ []);
|
|
31107
31232
|
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
31233
|
changingPassword = signal(false, ...ngDevMode ? [{ debugName: "changingPassword" }] : /* istanbul ignore next */ []);
|
|
31129
31234
|
allowChangePassword = computed(() => {
|
|
31130
31235
|
const useCredentials = this.client.config.authMode?.kind === "credentials";
|
|
@@ -31155,7 +31260,6 @@ var UserProfileFormComponent = class UserProfileFormComponent {
|
|
|
31155
31260
|
submission: { action: async () => this.saveEditedProperty() }
|
|
31156
31261
|
});
|
|
31157
31262
|
constructor() {
|
|
31158
|
-
this.currentLanguage.set(this.transloco.getActiveLang());
|
|
31159
31263
|
effect(() => {
|
|
31160
31264
|
if (this.userProfileResource.error()?.status === 404) this.createUserProfile();
|
|
31161
31265
|
});
|
|
@@ -31293,17 +31397,6 @@ var UserProfileFormComponent = class UserProfileFormComponent {
|
|
|
31293
31397
|
reader.readAsDataURL(element.files[0]);
|
|
31294
31398
|
reader.onload = () => void this.patchProperty("data", "profilePhoto", reader.result);
|
|
31295
31399
|
}
|
|
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
31400
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
31308
31401
|
minVersion: "12.0.0",
|
|
31309
31402
|
version: "22.2.1",
|
|
@@ -31318,18 +31411,10 @@ var UserProfileFormComponent = class UserProfileFormComponent {
|
|
|
31318
31411
|
type: UserProfileFormComponent,
|
|
31319
31412
|
isStandalone: true,
|
|
31320
31413
|
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
31414
|
host: { classAttribute: "flex flex-col h-full overflow-hidden" },
|
|
31330
31415
|
providers: [provideTranslocoScope("user-profile", "login")],
|
|
31331
31416
|
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",
|
|
31417
|
+
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
31418
|
dependencies: [
|
|
31334
31419
|
{
|
|
31335
31420
|
kind: "directive",
|
|
@@ -31422,45 +31507,6 @@ var UserProfileFormComponent = class UserProfileFormComponent {
|
|
|
31422
31507
|
"size"
|
|
31423
31508
|
]
|
|
31424
31509
|
},
|
|
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
31510
|
{
|
|
31465
31511
|
kind: "directive",
|
|
31466
31512
|
type: TextareaComponent,
|
|
@@ -31530,29 +31576,15 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
31530
31576
|
UserIcon,
|
|
31531
31577
|
AvatarImageComponent,
|
|
31532
31578
|
ButtonComponent,
|
|
31533
|
-
SelectComponent,
|
|
31534
|
-
SelectTriggerDirective,
|
|
31535
|
-
ChevronDownIcon,
|
|
31536
31579
|
TextareaComponent,
|
|
31537
31580
|
TextareaControlDirective,
|
|
31538
31581
|
ChangePasswordComponent
|
|
31539
31582
|
],
|
|
31540
31583
|
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"
|
|
31584
|
+
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
31585
|
}]
|
|
31543
31586
|
}],
|
|
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
|
-
}] }
|
|
31587
|
+
ctorParameters: () => []
|
|
31556
31588
|
});
|
|
31557
31589
|
function toRecord(keys, source) {
|
|
31558
31590
|
return Object.fromEntries(keys.map((key) => [key, String(source?.[key] ?? "")]));
|
|
@@ -31564,63 +31596,683 @@ function splitPropertyId(id) {
|
|
|
31564
31596
|
key: id.slice(separator + 1)
|
|
31565
31597
|
};
|
|
31566
31598
|
}
|
|
31567
|
-
var
|
|
31568
|
-
|
|
31569
|
-
|
|
31570
|
-
|
|
31571
|
-
|
|
31599
|
+
var SettingsAccountPanelComponent = class SettingsAccountPanelComponent {
|
|
31600
|
+
principalStore = inject(PrincipalStore);
|
|
31601
|
+
appStore = inject(AppStore);
|
|
31602
|
+
client = inject(ATOMIC_CLIENT);
|
|
31603
|
+
userOverride = inject(UserOverrideService);
|
|
31604
|
+
injector = inject(Injector);
|
|
31605
|
+
profileEnabled = computed(() => this.appStore.general()?.features?.userProfile?.enabled ?? false, ...ngDevMode ? [{ debugName: "profileEnabled" }] : /* istanbul ignore next */ []);
|
|
31606
|
+
username = computed(() => this.principalStore.name() || this.principalStore.email() || null, ...ngDevMode ? [{ debugName: "username" }] : /* istanbul ignore next */ []);
|
|
31607
|
+
allowChangePassword = computed(() => {
|
|
31608
|
+
const useCredentials = this.client.config.authMode?.kind === "credentials";
|
|
31609
|
+
const { allowChangePassword = false } = this.appStore.general()?.features || {};
|
|
31610
|
+
return allowChangePassword && useCredentials && (this.principalStore.editablePartition?.() || false);
|
|
31611
|
+
}, ...ngDevMode ? [{ debugName: "allowChangePassword" }] : /* istanbul ignore next */ []);
|
|
31612
|
+
resetSettings() {
|
|
31613
|
+
ResetUserSettings.call(void 0, { injector: this.injector });
|
|
31614
|
+
}
|
|
31615
|
+
overrideUser() {
|
|
31616
|
+
OverrideUser.call(void 0, { injector: this.injector });
|
|
31617
|
+
}
|
|
31618
|
+
revertOverride() {
|
|
31619
|
+
this.userOverride.override();
|
|
31572
31620
|
}
|
|
31573
31621
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
31574
31622
|
minVersion: "12.0.0",
|
|
31575
31623
|
version: "22.2.1",
|
|
31576
31624
|
ngImport: i0,
|
|
31577
|
-
type:
|
|
31625
|
+
type: SettingsAccountPanelComponent,
|
|
31578
31626
|
deps: [],
|
|
31579
31627
|
target: i0.ɵɵFactoryTarget.Component
|
|
31580
31628
|
});
|
|
31581
31629
|
static ɵcmp = i0.ɵɵngDeclareComponent({
|
|
31582
|
-
minVersion: "17.
|
|
31630
|
+
minVersion: "17.0.0",
|
|
31583
31631
|
version: "22.2.1",
|
|
31584
|
-
type:
|
|
31632
|
+
type: SettingsAccountPanelComponent,
|
|
31585
31633
|
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
|
-
}],
|
|
31634
|
+
selector: "settings-account-panel",
|
|
31635
|
+
providers: [provideTranslocoScope("settings")],
|
|
31595
31636
|
ngImport: i0,
|
|
31596
31637
|
template: `
|
|
31597
|
-
<
|
|
31598
|
-
|
|
31599
|
-
|
|
31600
|
-
|
|
31601
|
-
|
|
31638
|
+
<h2 class="text-(length:--size-2xl) font-semibold">{{ "settings.account.title" | transloco }}</h2>
|
|
31639
|
+
<p class="text-(--font-primary-muted)">{{ "settings.account.description" | transloco }}</p>
|
|
31640
|
+
|
|
31641
|
+
@if (profileEnabled()) {
|
|
31642
|
+
<user-profile-form class="mt-(--spacing-md) min-h-0 flex-1" />
|
|
31643
|
+
} @else if (allowChangePassword()) {
|
|
31644
|
+
<!-- No profile to show: the password is all the account offers, as it was in the user menu. -->
|
|
31645
|
+
<change-password
|
|
31646
|
+
class="mt-(--spacing-md)"
|
|
31647
|
+
[surface]="false"
|
|
31648
|
+
[username]="username()"
|
|
31649
|
+
[redirectAfterSuccess]="false"
|
|
31650
|
+
[redirectAfterCancel]="false" />
|
|
31651
|
+
}
|
|
31602
31652
|
|
|
31603
|
-
|
|
31604
|
-
|
|
31605
|
-
|
|
31606
|
-
|
|
31653
|
+
<!-- Account-wide actions, one row each: what it does on the left, the action on the right. -->
|
|
31654
|
+
<div class="mt-(--spacing-md) flex flex-col border-t border-(--stroke-primary-light)">
|
|
31655
|
+
@if (principalStore.isOverridingUser()) {
|
|
31656
|
+
<div class="flex items-center justify-between gap-(--spacing-md) border-b border-(--stroke-primary-light) py-(--spacing-md)">
|
|
31657
|
+
<div>
|
|
31658
|
+
<p id="settings-revert-override-title" class="font-semibold">{{ "settings.account.revertOverride.title" | transloco }}</p>
|
|
31659
|
+
<p id="settings-revert-override-description" class="text-(--font-primary-muted)">{{ "settings.account.revertOverride.description" | transloco }}</p>
|
|
31660
|
+
</div>
|
|
31661
|
+
<button type="button" variant="secondary" class="shrink-0" aria-describedby="settings-revert-override-description" (click)="revertOverride()">
|
|
31662
|
+
{{ "settings.account.revertOverride.action" | transloco }}
|
|
31663
|
+
</button>
|
|
31664
|
+
</div>
|
|
31665
|
+
} @else if (principalStore.allowUserOverride()) {
|
|
31666
|
+
<div class="flex items-center justify-between gap-(--spacing-md) border-b border-(--stroke-primary-light) py-(--spacing-md)">
|
|
31667
|
+
<div>
|
|
31668
|
+
<p id="settings-override-title" class="font-semibold">{{ "settings.account.overrideUser.title" | transloco }}</p>
|
|
31669
|
+
<p id="settings-override-description" class="text-(--font-primary-muted)">{{ "settings.account.overrideUser.description" | transloco }}</p>
|
|
31670
|
+
</div>
|
|
31671
|
+
<button type="button" variant="secondary" class="shrink-0" aria-describedby="settings-override-description" (click)="overrideUser()">
|
|
31672
|
+
{{ "settings.account.overrideUser.action" | transloco }}
|
|
31673
|
+
</button>
|
|
31674
|
+
</div>
|
|
31675
|
+
}
|
|
31676
|
+
|
|
31677
|
+
<div class="flex items-center justify-between gap-(--spacing-md) py-(--spacing-md)">
|
|
31678
|
+
<div>
|
|
31679
|
+
<p id="settings-clear-data-title" class="font-semibold">{{ "settings.account.clearData.title" | transloco }}</p>
|
|
31680
|
+
<p id="settings-clear-data-description" class="text-(--font-primary-muted)">{{ "settings.account.clearData.description" | transloco }}</p>
|
|
31681
|
+
</div>
|
|
31682
|
+
<!-- Destructive: the secondary variant re-coloured with the error tokens. Every state of the
|
|
31683
|
+
variant (hover, active, focus) sets its own colours, so each one is overridden here — a
|
|
31684
|
+
plain border/text colour would only win while the button is at rest. -->
|
|
31685
|
+
<button
|
|
31686
|
+
type="button"
|
|
31687
|
+
variant="secondary"
|
|
31688
|
+
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)"
|
|
31689
|
+
aria-describedby="settings-clear-data-description"
|
|
31690
|
+
(click)="resetSettings()">
|
|
31691
|
+
{{ "settings.account.clearData.action" | transloco }}
|
|
31692
|
+
</button>
|
|
31693
|
+
</div>
|
|
31607
31694
|
</div>
|
|
31608
31695
|
`,
|
|
31609
31696
|
isInline: true,
|
|
31610
31697
|
dependencies: [
|
|
31611
31698
|
{
|
|
31612
|
-
kind: "
|
|
31613
|
-
type:
|
|
31614
|
-
selector: "
|
|
31615
|
-
inputs: [
|
|
31616
|
-
|
|
31617
|
-
|
|
31618
|
-
|
|
31619
|
-
|
|
31620
|
-
|
|
31621
|
-
|
|
31622
|
-
|
|
31623
|
-
|
|
31699
|
+
kind: "directive",
|
|
31700
|
+
type: ButtonComponent,
|
|
31701
|
+
selector: "button",
|
|
31702
|
+
inputs: [
|
|
31703
|
+
"class",
|
|
31704
|
+
"variant",
|
|
31705
|
+
"scheme",
|
|
31706
|
+
"iconOnly",
|
|
31707
|
+
"size"
|
|
31708
|
+
]
|
|
31709
|
+
},
|
|
31710
|
+
{
|
|
31711
|
+
kind: "component",
|
|
31712
|
+
type: UserProfileFormComponent,
|
|
31713
|
+
selector: "user-profile-form, UserProfileForm, userprofileform"
|
|
31714
|
+
},
|
|
31715
|
+
{
|
|
31716
|
+
kind: "component",
|
|
31717
|
+
type: ChangePasswordComponent,
|
|
31718
|
+
selector: "change-password, ChangePassword, changepassword",
|
|
31719
|
+
inputs: [
|
|
31720
|
+
"username",
|
|
31721
|
+
"alert",
|
|
31722
|
+
"redirectAfterSuccess",
|
|
31723
|
+
"redirectAfterCancel",
|
|
31724
|
+
"surface",
|
|
31725
|
+
"passwords"
|
|
31726
|
+
],
|
|
31727
|
+
outputs: [
|
|
31728
|
+
"success",
|
|
31729
|
+
"cancel",
|
|
31730
|
+
"passwordsChange"
|
|
31731
|
+
]
|
|
31732
|
+
},
|
|
31733
|
+
{
|
|
31734
|
+
kind: "pipe",
|
|
31735
|
+
type: TranslocoPipe,
|
|
31736
|
+
name: "transloco"
|
|
31737
|
+
}
|
|
31738
|
+
]
|
|
31739
|
+
});
|
|
31740
|
+
};
|
|
31741
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
31742
|
+
minVersion: "12.0.0",
|
|
31743
|
+
version: "22.2.1",
|
|
31744
|
+
ngImport: i0,
|
|
31745
|
+
type: SettingsAccountPanelComponent,
|
|
31746
|
+
decorators: [{
|
|
31747
|
+
type: Component,
|
|
31748
|
+
args: [{
|
|
31749
|
+
selector: "settings-account-panel",
|
|
31750
|
+
imports: [
|
|
31751
|
+
TranslocoPipe,
|
|
31752
|
+
ButtonComponent,
|
|
31753
|
+
UserProfileFormComponent,
|
|
31754
|
+
ChangePasswordComponent
|
|
31755
|
+
],
|
|
31756
|
+
providers: [provideTranslocoScope("settings")],
|
|
31757
|
+
template: `
|
|
31758
|
+
<h2 class="text-(length:--size-2xl) font-semibold">{{ "settings.account.title" | transloco }}</h2>
|
|
31759
|
+
<p class="text-(--font-primary-muted)">{{ "settings.account.description" | transloco }}</p>
|
|
31760
|
+
|
|
31761
|
+
@if (profileEnabled()) {
|
|
31762
|
+
<user-profile-form class="mt-(--spacing-md) min-h-0 flex-1" />
|
|
31763
|
+
} @else if (allowChangePassword()) {
|
|
31764
|
+
<!-- No profile to show: the password is all the account offers, as it was in the user menu. -->
|
|
31765
|
+
<change-password
|
|
31766
|
+
class="mt-(--spacing-md)"
|
|
31767
|
+
[surface]="false"
|
|
31768
|
+
[username]="username()"
|
|
31769
|
+
[redirectAfterSuccess]="false"
|
|
31770
|
+
[redirectAfterCancel]="false" />
|
|
31771
|
+
}
|
|
31772
|
+
|
|
31773
|
+
<!-- Account-wide actions, one row each: what it does on the left, the action on the right. -->
|
|
31774
|
+
<div class="mt-(--spacing-md) flex flex-col border-t border-(--stroke-primary-light)">
|
|
31775
|
+
@if (principalStore.isOverridingUser()) {
|
|
31776
|
+
<div class="flex items-center justify-between gap-(--spacing-md) border-b border-(--stroke-primary-light) py-(--spacing-md)">
|
|
31777
|
+
<div>
|
|
31778
|
+
<p id="settings-revert-override-title" class="font-semibold">{{ "settings.account.revertOverride.title" | transloco }}</p>
|
|
31779
|
+
<p id="settings-revert-override-description" class="text-(--font-primary-muted)">{{ "settings.account.revertOverride.description" | transloco }}</p>
|
|
31780
|
+
</div>
|
|
31781
|
+
<button type="button" variant="secondary" class="shrink-0" aria-describedby="settings-revert-override-description" (click)="revertOverride()">
|
|
31782
|
+
{{ "settings.account.revertOverride.action" | transloco }}
|
|
31783
|
+
</button>
|
|
31784
|
+
</div>
|
|
31785
|
+
} @else if (principalStore.allowUserOverride()) {
|
|
31786
|
+
<div class="flex items-center justify-between gap-(--spacing-md) border-b border-(--stroke-primary-light) py-(--spacing-md)">
|
|
31787
|
+
<div>
|
|
31788
|
+
<p id="settings-override-title" class="font-semibold">{{ "settings.account.overrideUser.title" | transloco }}</p>
|
|
31789
|
+
<p id="settings-override-description" class="text-(--font-primary-muted)">{{ "settings.account.overrideUser.description" | transloco }}</p>
|
|
31790
|
+
</div>
|
|
31791
|
+
<button type="button" variant="secondary" class="shrink-0" aria-describedby="settings-override-description" (click)="overrideUser()">
|
|
31792
|
+
{{ "settings.account.overrideUser.action" | transloco }}
|
|
31793
|
+
</button>
|
|
31794
|
+
</div>
|
|
31795
|
+
}
|
|
31796
|
+
|
|
31797
|
+
<div class="flex items-center justify-between gap-(--spacing-md) py-(--spacing-md)">
|
|
31798
|
+
<div>
|
|
31799
|
+
<p id="settings-clear-data-title" class="font-semibold">{{ "settings.account.clearData.title" | transloco }}</p>
|
|
31800
|
+
<p id="settings-clear-data-description" class="text-(--font-primary-muted)">{{ "settings.account.clearData.description" | transloco }}</p>
|
|
31801
|
+
</div>
|
|
31802
|
+
<!-- Destructive: the secondary variant re-coloured with the error tokens. Every state of the
|
|
31803
|
+
variant (hover, active, focus) sets its own colours, so each one is overridden here — a
|
|
31804
|
+
plain border/text colour would only win while the button is at rest. -->
|
|
31805
|
+
<button
|
|
31806
|
+
type="button"
|
|
31807
|
+
variant="secondary"
|
|
31808
|
+
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)"
|
|
31809
|
+
aria-describedby="settings-clear-data-description"
|
|
31810
|
+
(click)="resetSettings()">
|
|
31811
|
+
{{ "settings.account.clearData.action" | transloco }}
|
|
31812
|
+
</button>
|
|
31813
|
+
</div>
|
|
31814
|
+
</div>
|
|
31815
|
+
`
|
|
31816
|
+
}]
|
|
31817
|
+
}]
|
|
31818
|
+
});
|
|
31819
|
+
const THEMES$1 = [
|
|
31820
|
+
"light",
|
|
31821
|
+
"dark",
|
|
31822
|
+
"system"
|
|
31823
|
+
];
|
|
31824
|
+
const LANGUAGES = [
|
|
31825
|
+
"en",
|
|
31826
|
+
"fr",
|
|
31827
|
+
"de"
|
|
31828
|
+
];
|
|
31829
|
+
const isTheme = (value) => THEMES$1.includes(value);
|
|
31830
|
+
const isSupportedLanguage = (value) => LANGUAGES.includes(value);
|
|
31831
|
+
var SettingsAppearancePanelComponent = class SettingsAppearancePanelComponent {
|
|
31832
|
+
userSettingsStore = inject(UserSettingsStore);
|
|
31833
|
+
transloco = inject(TranslocoService);
|
|
31834
|
+
document = inject(DOCUMENT);
|
|
31835
|
+
lang = toSignal(this.transloco.langChanges$, { initialValue: this.transloco.getActiveLang() });
|
|
31836
|
+
loadedTranslations = toSignal(this.transloco.events$.pipe(filter((event) => event.type === "translationLoadSuccess"), scan((count) => count + 1, 0)), { initialValue: 0 });
|
|
31837
|
+
themeCodes = [...THEMES$1];
|
|
31838
|
+
languageCodes = [...LANGUAGES];
|
|
31839
|
+
themeIcons = {
|
|
31840
|
+
light: SunBrightIcon,
|
|
31841
|
+
dark: MoonIcon,
|
|
31842
|
+
system: DesktopIcon
|
|
31843
|
+
};
|
|
31844
|
+
languageIcons = {
|
|
31845
|
+
en: FlagEnglishIcon,
|
|
31846
|
+
fr: FlagFrenchIcon,
|
|
31847
|
+
de: FlagGermanIcon
|
|
31848
|
+
};
|
|
31849
|
+
languageLabels = {
|
|
31850
|
+
en: "English",
|
|
31851
|
+
fr: "Français",
|
|
31852
|
+
de: "Deutsch"
|
|
31853
|
+
};
|
|
31854
|
+
currentTheme = computed(() => this.userSettingsStore.userTheme() ?? "system", ...ngDevMode ? [{ debugName: "currentTheme" }] : /* istanbul ignore next */ []);
|
|
31855
|
+
currentLanguage = computed(() => this.lang(), ...ngDevMode ? [{ debugName: "currentLanguage" }] : /* istanbul ignore next */ []);
|
|
31856
|
+
themeLabel = (code) => {
|
|
31857
|
+
this.lang();
|
|
31858
|
+
this.loadedTranslations();
|
|
31859
|
+
return isTheme(code) ? this.transloco.translate(`settings.appearance.${code}Mode`) : "";
|
|
31860
|
+
};
|
|
31861
|
+
languageLabel = (code) => isSupportedLanguage(code) ? this.languageLabels[code] : code ?? "";
|
|
31862
|
+
themeIcon(code) {
|
|
31863
|
+
return isTheme(code) ? this.themeIcons[code] : null;
|
|
31864
|
+
}
|
|
31865
|
+
languageIcon(code) {
|
|
31866
|
+
return isSupportedLanguage(code) ? this.languageIcons[code] : null;
|
|
31867
|
+
}
|
|
31868
|
+
async onThemeChange(code) {
|
|
31869
|
+
if (!isTheme(code)) return;
|
|
31870
|
+
try {
|
|
31871
|
+
await this.userSettingsStore.setUserTheme(code);
|
|
31872
|
+
} catch (err) {
|
|
31873
|
+
error("set user theme failed", err);
|
|
31874
|
+
return;
|
|
31875
|
+
}
|
|
31876
|
+
const dark = code === "dark" || code === "system" && window.matchMedia?.("(prefers-color-scheme: dark)").matches;
|
|
31877
|
+
this.document.documentElement.classList.toggle("dark", dark);
|
|
31878
|
+
}
|
|
31879
|
+
onLanguageChange(code) {
|
|
31880
|
+
if (!isSupportedLanguage(code)) return;
|
|
31881
|
+
this.userSettingsStore.updateLanguage(code).catch((err) => error("update language failed", err));
|
|
31882
|
+
if (this.transloco.getActiveLang() !== code) this.transloco.setActiveLang(code);
|
|
31883
|
+
}
|
|
31884
|
+
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
31885
|
+
minVersion: "12.0.0",
|
|
31886
|
+
version: "22.2.1",
|
|
31887
|
+
ngImport: i0,
|
|
31888
|
+
type: SettingsAppearancePanelComponent,
|
|
31889
|
+
deps: [],
|
|
31890
|
+
target: i0.ɵɵFactoryTarget.Component
|
|
31891
|
+
});
|
|
31892
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({
|
|
31893
|
+
minVersion: "14.0.0",
|
|
31894
|
+
version: "22.2.1",
|
|
31895
|
+
type: SettingsAppearancePanelComponent,
|
|
31896
|
+
isStandalone: true,
|
|
31897
|
+
selector: "settings-appearance-panel",
|
|
31898
|
+
providers: [provideTranslocoScope("settings")],
|
|
31899
|
+
ngImport: i0,
|
|
31900
|
+
template: `
|
|
31901
|
+
<h2 class="text-(length:--size-2xl) font-semibold">{{ "settings.appearance.title" | transloco }}</h2>
|
|
31902
|
+
<p class="text-(--font-primary-muted)">{{ "settings.appearance.description" | transloco }}</p>
|
|
31903
|
+
|
|
31904
|
+
<div class="mt-(--spacing-lg) flex items-start justify-between gap-(--spacing-md)">
|
|
31905
|
+
<div>
|
|
31906
|
+
<label id="settings-theme-label" class="font-semibold" for="settings-theme">{{ "settings.appearance.theme" | transloco }}</label>
|
|
31907
|
+
<p id="settings-theme-description" class="text-(--font-primary-muted)">{{ "settings.appearance.themeDescription" | transloco }}</p>
|
|
31908
|
+
</div>
|
|
31909
|
+
<SelectMenu class="w-fit shrink-0" size="md" [options]="themeCodes" [label]="themeLabel" [value]="currentTheme()" (valueChange)="onThemeChange($event)">
|
|
31910
|
+
<button selectTrigger type="button" id="settings-theme" class="select-trigger" aria-describedby="settings-theme-description">
|
|
31911
|
+
<ng-container *ngComponentOutlet="themeIcon(currentTheme())" />
|
|
31912
|
+
<span class="select-trigger-label">{{ themeLabel(currentTheme()) }}</span>
|
|
31913
|
+
<chevron-down-icon class="select-chevron" />
|
|
31914
|
+
</button>
|
|
31915
|
+
<!-- The default option rendering is the label alone: the icon needs its own template. -->
|
|
31916
|
+
<ng-template #selectOption let-option>
|
|
31917
|
+
<span class="flex items-center gap-(--spacing-xs)">
|
|
31918
|
+
<ng-container *ngComponentOutlet="themeIcon(option)" />
|
|
31919
|
+
{{ themeLabel(option) }}
|
|
31920
|
+
</span>
|
|
31921
|
+
</ng-template>
|
|
31922
|
+
</SelectMenu>
|
|
31923
|
+
</div>
|
|
31924
|
+
|
|
31925
|
+
<hr class="my-(--spacing-md) border-(--stroke-primary-light)" />
|
|
31926
|
+
|
|
31927
|
+
<div class="flex items-start justify-between gap-(--spacing-md)">
|
|
31928
|
+
<div>
|
|
31929
|
+
<label id="settings-language-label" class="font-semibold" for="settings-language">{{ "settings.appearance.language" | transloco }}</label>
|
|
31930
|
+
<p id="settings-language-description" class="text-(--font-primary-muted)">{{ "settings.appearance.languageDescription" | transloco }}</p>
|
|
31931
|
+
</div>
|
|
31932
|
+
<SelectMenu
|
|
31933
|
+
class="w-fit shrink-0"
|
|
31934
|
+
size="md"
|
|
31935
|
+
[options]="languageCodes"
|
|
31936
|
+
[label]="languageLabel"
|
|
31937
|
+
[value]="currentLanguage()"
|
|
31938
|
+
(valueChange)="onLanguageChange($event)">
|
|
31939
|
+
<button selectTrigger type="button" id="settings-language" class="select-trigger" aria-describedby="settings-language-description">
|
|
31940
|
+
<ng-container *ngComponentOutlet="languageIcon(currentLanguage())" />
|
|
31941
|
+
<span class="select-trigger-label">{{ languageLabel(currentLanguage()) }}</span>
|
|
31942
|
+
<chevron-down-icon class="select-chevron" />
|
|
31943
|
+
</button>
|
|
31944
|
+
<ng-template #selectOption let-option>
|
|
31945
|
+
<span class="flex items-center gap-(--spacing-xs)">
|
|
31946
|
+
<ng-container *ngComponentOutlet="languageIcon(option)" />
|
|
31947
|
+
{{ languageLabel(option) }}
|
|
31948
|
+
</span>
|
|
31949
|
+
</ng-template>
|
|
31950
|
+
</SelectMenu>
|
|
31951
|
+
</div>
|
|
31952
|
+
`,
|
|
31953
|
+
isInline: true,
|
|
31954
|
+
dependencies: [
|
|
31955
|
+
{
|
|
31956
|
+
kind: "directive",
|
|
31957
|
+
type: NgComponentOutlet,
|
|
31958
|
+
selector: "[ngComponentOutlet]",
|
|
31959
|
+
inputs: [
|
|
31960
|
+
"ngComponentOutlet",
|
|
31961
|
+
"ngComponentOutletInputs",
|
|
31962
|
+
"ngComponentOutletInjector",
|
|
31963
|
+
"ngComponentOutletEnvironmentInjector",
|
|
31964
|
+
"ngComponentOutletContent",
|
|
31965
|
+
"ngComponentOutletNgModule"
|
|
31966
|
+
],
|
|
31967
|
+
exportAs: ["ngComponentOutlet"]
|
|
31968
|
+
},
|
|
31969
|
+
{
|
|
31970
|
+
kind: "component",
|
|
31971
|
+
type: SelectComponent,
|
|
31972
|
+
selector: "SelectMenu,select-menu",
|
|
31973
|
+
inputs: [
|
|
31974
|
+
"value",
|
|
31975
|
+
"options",
|
|
31976
|
+
"placement",
|
|
31977
|
+
"size",
|
|
31978
|
+
"fieldLabel",
|
|
31979
|
+
"aria-label",
|
|
31980
|
+
"placeholder",
|
|
31981
|
+
"hint",
|
|
31982
|
+
"disabled",
|
|
31983
|
+
"class",
|
|
31984
|
+
"selectionMode",
|
|
31985
|
+
"multiple",
|
|
31986
|
+
"wrap",
|
|
31987
|
+
"key",
|
|
31988
|
+
"label",
|
|
31989
|
+
"optionDisabled",
|
|
31990
|
+
"noOptionsText",
|
|
31991
|
+
"contentClass"
|
|
31992
|
+
],
|
|
31993
|
+
outputs: ["valueChange", "touch"],
|
|
31994
|
+
exportAs: ["select"]
|
|
31995
|
+
},
|
|
31996
|
+
{
|
|
31997
|
+
kind: "directive",
|
|
31998
|
+
type: SelectTriggerDirective,
|
|
31999
|
+
selector: "[selectTrigger]",
|
|
32000
|
+
exportAs: ["selectTrigger"]
|
|
32001
|
+
},
|
|
32002
|
+
{
|
|
32003
|
+
kind: "component",
|
|
32004
|
+
type: ChevronDownIcon,
|
|
32005
|
+
selector: "chevron-down, chevron-down-icon, ChevronDown, ChevronDownIcon",
|
|
32006
|
+
inputs: ["class"]
|
|
32007
|
+
},
|
|
32008
|
+
{
|
|
32009
|
+
kind: "pipe",
|
|
32010
|
+
type: TranslocoPipe,
|
|
32011
|
+
name: "transloco"
|
|
32012
|
+
}
|
|
32013
|
+
]
|
|
32014
|
+
});
|
|
32015
|
+
};
|
|
32016
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
32017
|
+
minVersion: "12.0.0",
|
|
32018
|
+
version: "22.2.1",
|
|
32019
|
+
ngImport: i0,
|
|
32020
|
+
type: SettingsAppearancePanelComponent,
|
|
32021
|
+
decorators: [{
|
|
32022
|
+
type: Component,
|
|
32023
|
+
args: [{
|
|
32024
|
+
selector: "settings-appearance-panel",
|
|
32025
|
+
imports: [
|
|
32026
|
+
NgComponentOutlet,
|
|
32027
|
+
TranslocoPipe,
|
|
32028
|
+
SelectComponent,
|
|
32029
|
+
SelectTriggerDirective,
|
|
32030
|
+
ChevronDownIcon
|
|
32031
|
+
],
|
|
32032
|
+
providers: [provideTranslocoScope("settings")],
|
|
32033
|
+
template: `
|
|
32034
|
+
<h2 class="text-(length:--size-2xl) font-semibold">{{ "settings.appearance.title" | transloco }}</h2>
|
|
32035
|
+
<p class="text-(--font-primary-muted)">{{ "settings.appearance.description" | transloco }}</p>
|
|
32036
|
+
|
|
32037
|
+
<div class="mt-(--spacing-lg) flex items-start justify-between gap-(--spacing-md)">
|
|
32038
|
+
<div>
|
|
32039
|
+
<label id="settings-theme-label" class="font-semibold" for="settings-theme">{{ "settings.appearance.theme" | transloco }}</label>
|
|
32040
|
+
<p id="settings-theme-description" class="text-(--font-primary-muted)">{{ "settings.appearance.themeDescription" | transloco }}</p>
|
|
32041
|
+
</div>
|
|
32042
|
+
<SelectMenu class="w-fit shrink-0" size="md" [options]="themeCodes" [label]="themeLabel" [value]="currentTheme()" (valueChange)="onThemeChange($event)">
|
|
32043
|
+
<button selectTrigger type="button" id="settings-theme" class="select-trigger" aria-describedby="settings-theme-description">
|
|
32044
|
+
<ng-container *ngComponentOutlet="themeIcon(currentTheme())" />
|
|
32045
|
+
<span class="select-trigger-label">{{ themeLabel(currentTheme()) }}</span>
|
|
32046
|
+
<chevron-down-icon class="select-chevron" />
|
|
32047
|
+
</button>
|
|
32048
|
+
<!-- The default option rendering is the label alone: the icon needs its own template. -->
|
|
32049
|
+
<ng-template #selectOption let-option>
|
|
32050
|
+
<span class="flex items-center gap-(--spacing-xs)">
|
|
32051
|
+
<ng-container *ngComponentOutlet="themeIcon(option)" />
|
|
32052
|
+
{{ themeLabel(option) }}
|
|
32053
|
+
</span>
|
|
32054
|
+
</ng-template>
|
|
32055
|
+
</SelectMenu>
|
|
32056
|
+
</div>
|
|
32057
|
+
|
|
32058
|
+
<hr class="my-(--spacing-md) border-(--stroke-primary-light)" />
|
|
32059
|
+
|
|
32060
|
+
<div class="flex items-start justify-between gap-(--spacing-md)">
|
|
32061
|
+
<div>
|
|
32062
|
+
<label id="settings-language-label" class="font-semibold" for="settings-language">{{ "settings.appearance.language" | transloco }}</label>
|
|
32063
|
+
<p id="settings-language-description" class="text-(--font-primary-muted)">{{ "settings.appearance.languageDescription" | transloco }}</p>
|
|
32064
|
+
</div>
|
|
32065
|
+
<SelectMenu
|
|
32066
|
+
class="w-fit shrink-0"
|
|
32067
|
+
size="md"
|
|
32068
|
+
[options]="languageCodes"
|
|
32069
|
+
[label]="languageLabel"
|
|
32070
|
+
[value]="currentLanguage()"
|
|
32071
|
+
(valueChange)="onLanguageChange($event)">
|
|
32072
|
+
<button selectTrigger type="button" id="settings-language" class="select-trigger" aria-describedby="settings-language-description">
|
|
32073
|
+
<ng-container *ngComponentOutlet="languageIcon(currentLanguage())" />
|
|
32074
|
+
<span class="select-trigger-label">{{ languageLabel(currentLanguage()) }}</span>
|
|
32075
|
+
<chevron-down-icon class="select-chevron" />
|
|
32076
|
+
</button>
|
|
32077
|
+
<ng-template #selectOption let-option>
|
|
32078
|
+
<span class="flex items-center gap-(--spacing-xs)">
|
|
32079
|
+
<ng-container *ngComponentOutlet="languageIcon(option)" />
|
|
32080
|
+
{{ languageLabel(option) }}
|
|
32081
|
+
</span>
|
|
32082
|
+
</ng-template>
|
|
32083
|
+
</SelectMenu>
|
|
32084
|
+
</div>
|
|
32085
|
+
`
|
|
32086
|
+
}]
|
|
32087
|
+
}]
|
|
32088
|
+
});
|
|
32089
|
+
var SettingsShortcutsPanelComponent = class SettingsShortcutsPanelComponent {
|
|
32090
|
+
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
32091
|
+
minVersion: "12.0.0",
|
|
32092
|
+
version: "22.2.1",
|
|
32093
|
+
ngImport: i0,
|
|
32094
|
+
type: SettingsShortcutsPanelComponent,
|
|
32095
|
+
deps: [],
|
|
32096
|
+
target: i0.ɵɵFactoryTarget.Component
|
|
32097
|
+
});
|
|
32098
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({
|
|
32099
|
+
minVersion: "14.0.0",
|
|
32100
|
+
version: "22.2.1",
|
|
32101
|
+
type: SettingsShortcutsPanelComponent,
|
|
32102
|
+
isStandalone: true,
|
|
32103
|
+
selector: "settings-shortcuts-panel",
|
|
32104
|
+
providers: [provideTranslocoScope("settings")],
|
|
32105
|
+
ngImport: i0,
|
|
32106
|
+
template: `
|
|
32107
|
+
<h2 class="text-(length:--size-2xl) font-semibold">{{ "settings.shortcuts.title" | transloco }}</h2>
|
|
32108
|
+
<p class="mb-(--spacing-md) text-(--font-primary-muted)">{{ "settings.shortcuts.description" | transloco }}</p>
|
|
32109
|
+
<keyboard-shortcuts-list />
|
|
32110
|
+
`,
|
|
32111
|
+
isInline: true,
|
|
32112
|
+
dependencies: [{
|
|
32113
|
+
kind: "component",
|
|
32114
|
+
type: KeyboardShortcutsListComponent,
|
|
32115
|
+
selector: "keyboard-shortcuts-list"
|
|
32116
|
+
}, {
|
|
32117
|
+
kind: "pipe",
|
|
32118
|
+
type: TranslocoPipe,
|
|
32119
|
+
name: "transloco"
|
|
32120
|
+
}]
|
|
32121
|
+
});
|
|
32122
|
+
};
|
|
32123
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
32124
|
+
minVersion: "12.0.0",
|
|
32125
|
+
version: "22.2.1",
|
|
32126
|
+
ngImport: i0,
|
|
32127
|
+
type: SettingsShortcutsPanelComponent,
|
|
32128
|
+
decorators: [{
|
|
32129
|
+
type: Component,
|
|
32130
|
+
args: [{
|
|
32131
|
+
selector: "settings-shortcuts-panel",
|
|
32132
|
+
imports: [TranslocoPipe, KeyboardShortcutsListComponent],
|
|
32133
|
+
providers: [provideTranslocoScope("settings")],
|
|
32134
|
+
template: `
|
|
32135
|
+
<h2 class="text-(length:--size-2xl) font-semibold">{{ "settings.shortcuts.title" | transloco }}</h2>
|
|
32136
|
+
<p class="mb-(--spacing-md) text-(--font-primary-muted)">{{ "settings.shortcuts.description" | transloco }}</p>
|
|
32137
|
+
<keyboard-shortcuts-list />
|
|
32138
|
+
`
|
|
32139
|
+
}]
|
|
32140
|
+
}]
|
|
32141
|
+
});
|
|
32142
|
+
let active;
|
|
32143
|
+
var SettingsDialogComponent = class SettingsDialogComponent {
|
|
32144
|
+
call = injectCallRef();
|
|
32145
|
+
dialog = viewChild(DialogComponent, ...ngDevMode ? [{ debugName: "dialog" }] : /* istanbul ignore next */ []);
|
|
32146
|
+
items = [
|
|
32147
|
+
{
|
|
32148
|
+
id: "account",
|
|
32149
|
+
labelKey: "settings.account.nav"
|
|
32150
|
+
},
|
|
32151
|
+
{
|
|
32152
|
+
id: "appearance",
|
|
32153
|
+
labelKey: "settings.appearance.nav"
|
|
32154
|
+
},
|
|
32155
|
+
{
|
|
32156
|
+
id: "shortcuts",
|
|
32157
|
+
labelKey: "settings.shortcuts.nav"
|
|
32158
|
+
}
|
|
32159
|
+
];
|
|
32160
|
+
panel = signal(this.call.props()?.panel ?? "account", ...ngDevMode ? [{ debugName: "panel" }] : /* istanbul ignore next */ []);
|
|
32161
|
+
duplicate = active !== void 0;
|
|
32162
|
+
constructor() {
|
|
32163
|
+
if (active) {
|
|
32164
|
+
active.panel.set(this.panel());
|
|
32165
|
+
afterNextRender(() => this.call.end("dialog-close"));
|
|
32166
|
+
return;
|
|
32167
|
+
}
|
|
32168
|
+
active = this;
|
|
32169
|
+
inject(DestroyRef).onDestroy(() => {
|
|
32170
|
+
if (active === this) active = void 0;
|
|
32171
|
+
});
|
|
32172
|
+
afterNextRender(() => this.dialog()?.open());
|
|
32173
|
+
}
|
|
32174
|
+
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
32175
|
+
minVersion: "12.0.0",
|
|
32176
|
+
version: "22.2.1",
|
|
32177
|
+
ngImport: i0,
|
|
32178
|
+
type: SettingsDialogComponent,
|
|
32179
|
+
deps: [],
|
|
32180
|
+
target: i0.ɵɵFactoryTarget.Component
|
|
32181
|
+
});
|
|
32182
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({
|
|
32183
|
+
minVersion: "17.0.0",
|
|
32184
|
+
version: "22.2.1",
|
|
32185
|
+
type: SettingsDialogComponent,
|
|
32186
|
+
isStandalone: true,
|
|
32187
|
+
selector: "settings-dialog",
|
|
32188
|
+
providers: [provideTranslocoScope("settings")],
|
|
32189
|
+
viewQueries: [{
|
|
32190
|
+
propertyName: "dialog",
|
|
32191
|
+
first: true,
|
|
32192
|
+
predicate: DialogComponent,
|
|
32193
|
+
descendants: true,
|
|
32194
|
+
isSignal: true
|
|
32195
|
+
}],
|
|
32196
|
+
ngImport: i0,
|
|
32197
|
+
template: `
|
|
32198
|
+
@if (!duplicate) {
|
|
32199
|
+
<div dialog #dialog="dialog" [closeLabel]="'settings.close' | transloco" (closed)="call.end($event)">
|
|
32200
|
+
<DialogContent size="xl" class="h-[min(48rem,calc(100dvh-2rem))] flex-row! items-stretch!">
|
|
32201
|
+
<div class="flex w-64 shrink-0 flex-col gap-(--spacing-3xs) bg-(--layer-page-on-layer) p-(--spacing-sm)">
|
|
32202
|
+
<button
|
|
32203
|
+
type="button"
|
|
32204
|
+
variant="none"
|
|
32205
|
+
class="modal-close mb-(--spacing-sm)"
|
|
32206
|
+
[attr.aria-label]="'settings.close' | transloco"
|
|
32207
|
+
(click)="dialog.close()">
|
|
32208
|
+
<XMarkIcon aria-hidden="true" />
|
|
32209
|
+
</button>
|
|
32210
|
+
|
|
32211
|
+
<!-- Panels are a navigation, not a tablist: each one is a button, the current one is
|
|
32212
|
+
flagged with aria-current, which is what a screen reader reads out. -->
|
|
32213
|
+
<nav [attr.aria-label]="'settings.title' | transloco" class="flex flex-col gap-(--spacing-3xs)">
|
|
32214
|
+
@for (item of items; track item.id) {
|
|
32215
|
+
<button
|
|
32216
|
+
type="button"
|
|
32217
|
+
variant="none"
|
|
32218
|
+
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)"
|
|
32219
|
+
[attr.aria-current]="panel() === item.id ? 'page' : null"
|
|
32220
|
+
(click)="panel.set(item.id)">
|
|
32221
|
+
@switch (item.id) {
|
|
32222
|
+
@case ("account") {
|
|
32223
|
+
<user-icon aria-hidden="true" />
|
|
32224
|
+
}
|
|
32225
|
+
@case ("appearance") {
|
|
32226
|
+
<palette-icon aria-hidden="true" />
|
|
32227
|
+
}
|
|
32228
|
+
@case ("shortcuts") {
|
|
32229
|
+
<keyboard-icon aria-hidden="true" />
|
|
32230
|
+
}
|
|
32231
|
+
}
|
|
32232
|
+
{{ item.labelKey | transloco }}
|
|
32233
|
+
</button>
|
|
32234
|
+
}
|
|
32235
|
+
</nav>
|
|
32236
|
+
</div>
|
|
32237
|
+
|
|
32238
|
+
<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)">
|
|
32239
|
+
@switch (panel()) {
|
|
32240
|
+
@case ("account") {
|
|
32241
|
+
<settings-account-panel class="flex min-h-0 flex-1 flex-col" />
|
|
32242
|
+
}
|
|
32243
|
+
@case ("appearance") {
|
|
32244
|
+
<settings-appearance-panel />
|
|
32245
|
+
}
|
|
32246
|
+
@case ("shortcuts") {
|
|
32247
|
+
<settings-shortcuts-panel />
|
|
32248
|
+
}
|
|
32249
|
+
}
|
|
32250
|
+
</div>
|
|
32251
|
+
</DialogContent>
|
|
32252
|
+
</div>
|
|
32253
|
+
}
|
|
32254
|
+
`,
|
|
32255
|
+
isInline: true,
|
|
32256
|
+
dependencies: [
|
|
32257
|
+
{
|
|
32258
|
+
kind: "directive",
|
|
32259
|
+
type: ButtonComponent,
|
|
32260
|
+
selector: "button",
|
|
32261
|
+
inputs: [
|
|
32262
|
+
"class",
|
|
32263
|
+
"variant",
|
|
32264
|
+
"scheme",
|
|
32265
|
+
"iconOnly",
|
|
32266
|
+
"size"
|
|
32267
|
+
]
|
|
32268
|
+
},
|
|
32269
|
+
{
|
|
32270
|
+
kind: "component",
|
|
32271
|
+
type: DialogComponent,
|
|
32272
|
+
selector: "dialog, [dialog]",
|
|
32273
|
+
inputs: ["closeLabel"],
|
|
32274
|
+
outputs: ["closed"],
|
|
32275
|
+
exportAs: ["dialog"]
|
|
31624
32276
|
},
|
|
31625
32277
|
{
|
|
31626
32278
|
kind: "directive",
|
|
@@ -31633,23 +32285,43 @@ var UserProfileDialog = class UserProfileDialog {
|
|
|
31633
32285
|
]
|
|
31634
32286
|
},
|
|
31635
32287
|
{
|
|
31636
|
-
kind: "
|
|
31637
|
-
type:
|
|
31638
|
-
selector: "
|
|
32288
|
+
kind: "component",
|
|
32289
|
+
type: UserIcon,
|
|
32290
|
+
selector: "user, user-icon, User, UserIcon",
|
|
31639
32291
|
inputs: ["class"]
|
|
31640
32292
|
},
|
|
31641
32293
|
{
|
|
31642
32294
|
kind: "component",
|
|
31643
|
-
type:
|
|
31644
|
-
selector: "
|
|
31645
|
-
inputs: ["
|
|
31646
|
-
outputs: ["currentLanguageChange"]
|
|
32295
|
+
type: PaletteIcon,
|
|
32296
|
+
selector: "palette, palette-icon, Palette, PaletteIcon",
|
|
32297
|
+
inputs: ["class"]
|
|
31647
32298
|
},
|
|
31648
32299
|
{
|
|
31649
|
-
kind: "
|
|
31650
|
-
type:
|
|
31651
|
-
selector: "
|
|
31652
|
-
inputs: ["
|
|
32300
|
+
kind: "component",
|
|
32301
|
+
type: KeyboardIcon,
|
|
32302
|
+
selector: "keyboard, keyboard-icon, Keyboard, KeyboardIcon",
|
|
32303
|
+
inputs: ["class"]
|
|
32304
|
+
},
|
|
32305
|
+
{
|
|
32306
|
+
kind: "component",
|
|
32307
|
+
type: XMarkIcon,
|
|
32308
|
+
selector: "x-mark, x-mark-icon, XMark, XMarkIcon",
|
|
32309
|
+
inputs: ["class"]
|
|
32310
|
+
},
|
|
32311
|
+
{
|
|
32312
|
+
kind: "component",
|
|
32313
|
+
type: SettingsAccountPanelComponent,
|
|
32314
|
+
selector: "settings-account-panel"
|
|
32315
|
+
},
|
|
32316
|
+
{
|
|
32317
|
+
kind: "component",
|
|
32318
|
+
type: SettingsAppearancePanelComponent,
|
|
32319
|
+
selector: "settings-appearance-panel"
|
|
32320
|
+
},
|
|
32321
|
+
{
|
|
32322
|
+
kind: "component",
|
|
32323
|
+
type: SettingsShortcutsPanelComponent,
|
|
32324
|
+
selector: "settings-shortcuts-panel"
|
|
31653
32325
|
},
|
|
31654
32326
|
{
|
|
31655
32327
|
kind: "pipe",
|
|
@@ -31663,33 +32335,82 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
31663
32335
|
minVersion: "12.0.0",
|
|
31664
32336
|
version: "22.2.1",
|
|
31665
32337
|
ngImport: i0,
|
|
31666
|
-
type:
|
|
32338
|
+
type: SettingsDialogComponent,
|
|
31667
32339
|
decorators: [{
|
|
31668
32340
|
type: Component,
|
|
31669
32341
|
args: [{
|
|
31670
|
-
selector: "
|
|
32342
|
+
selector: "settings-dialog",
|
|
31671
32343
|
imports: [
|
|
32344
|
+
TranslocoPipe,
|
|
32345
|
+
ButtonComponent,
|
|
31672
32346
|
DialogComponent,
|
|
31673
|
-
DialogHeaderComponent,
|
|
31674
32347
|
DialogContentComponent,
|
|
31675
|
-
|
|
31676
|
-
|
|
31677
|
-
|
|
31678
|
-
|
|
32348
|
+
UserIcon,
|
|
32349
|
+
PaletteIcon,
|
|
32350
|
+
KeyboardIcon,
|
|
32351
|
+
XMarkIcon,
|
|
32352
|
+
SettingsAccountPanelComponent,
|
|
32353
|
+
SettingsAppearancePanelComponent,
|
|
32354
|
+
SettingsShortcutsPanelComponent
|
|
31679
32355
|
],
|
|
31680
|
-
providers: [provideTranslocoScope("
|
|
32356
|
+
providers: [provideTranslocoScope("settings")],
|
|
31681
32357
|
template: `
|
|
31682
|
-
|
|
31683
|
-
<
|
|
31684
|
-
<
|
|
31685
|
-
<
|
|
31686
|
-
|
|
32358
|
+
@if (!duplicate) {
|
|
32359
|
+
<div dialog #dialog="dialog" [closeLabel]="'settings.close' | transloco" (closed)="call.end($event)">
|
|
32360
|
+
<DialogContent size="xl" class="h-[min(48rem,calc(100dvh-2rem))] flex-row! items-stretch!">
|
|
32361
|
+
<div class="flex w-64 shrink-0 flex-col gap-(--spacing-3xs) bg-(--layer-page-on-layer) p-(--spacing-sm)">
|
|
32362
|
+
<button
|
|
32363
|
+
type="button"
|
|
32364
|
+
variant="none"
|
|
32365
|
+
class="modal-close mb-(--spacing-sm)"
|
|
32366
|
+
[attr.aria-label]="'settings.close' | transloco"
|
|
32367
|
+
(click)="dialog.close()">
|
|
32368
|
+
<XMarkIcon aria-hidden="true" />
|
|
32369
|
+
</button>
|
|
31687
32370
|
|
|
31688
|
-
|
|
31689
|
-
|
|
31690
|
-
|
|
31691
|
-
|
|
31692
|
-
|
|
32371
|
+
<!-- Panels are a navigation, not a tablist: each one is a button, the current one is
|
|
32372
|
+
flagged with aria-current, which is what a screen reader reads out. -->
|
|
32373
|
+
<nav [attr.aria-label]="'settings.title' | transloco" class="flex flex-col gap-(--spacing-3xs)">
|
|
32374
|
+
@for (item of items; track item.id) {
|
|
32375
|
+
<button
|
|
32376
|
+
type="button"
|
|
32377
|
+
variant="none"
|
|
32378
|
+
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)"
|
|
32379
|
+
[attr.aria-current]="panel() === item.id ? 'page' : null"
|
|
32380
|
+
(click)="panel.set(item.id)">
|
|
32381
|
+
@switch (item.id) {
|
|
32382
|
+
@case ("account") {
|
|
32383
|
+
<user-icon aria-hidden="true" />
|
|
32384
|
+
}
|
|
32385
|
+
@case ("appearance") {
|
|
32386
|
+
<palette-icon aria-hidden="true" />
|
|
32387
|
+
}
|
|
32388
|
+
@case ("shortcuts") {
|
|
32389
|
+
<keyboard-icon aria-hidden="true" />
|
|
32390
|
+
}
|
|
32391
|
+
}
|
|
32392
|
+
{{ item.labelKey | transloco }}
|
|
32393
|
+
</button>
|
|
32394
|
+
}
|
|
32395
|
+
</nav>
|
|
32396
|
+
</div>
|
|
32397
|
+
|
|
32398
|
+
<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)">
|
|
32399
|
+
@switch (panel()) {
|
|
32400
|
+
@case ("account") {
|
|
32401
|
+
<settings-account-panel class="flex min-h-0 flex-1 flex-col" />
|
|
32402
|
+
}
|
|
32403
|
+
@case ("appearance") {
|
|
32404
|
+
<settings-appearance-panel />
|
|
32405
|
+
}
|
|
32406
|
+
@case ("shortcuts") {
|
|
32407
|
+
<settings-shortcuts-panel />
|
|
32408
|
+
}
|
|
32409
|
+
}
|
|
32410
|
+
</div>
|
|
32411
|
+
</DialogContent>
|
|
32412
|
+
</div>
|
|
32413
|
+
}
|
|
31693
32414
|
`
|
|
31694
32415
|
}]
|
|
31695
32416
|
}],
|
|
@@ -31699,7 +32420,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
31699
32420
|
args: [i0.forwardRef(() => DialogComponent), { isSignal: true }]
|
|
31700
32421
|
}] }
|
|
31701
32422
|
});
|
|
31702
|
-
const
|
|
32423
|
+
const Settings = createCallable(SettingsDialogComponent);
|
|
31703
32424
|
const auditInterceptorFn = (request, next) => {
|
|
31704
32425
|
if (!request.url.includes("api/v1")) return next(request);
|
|
31705
32426
|
if (isJsonable(request.body) && !(request.body instanceof HttpParams)) addAuditAdditionalInfo(request.body);
|
|
@@ -32150,6 +32871,6 @@ function provideSessionExpiry(options = {}) {
|
|
|
32150
32871
|
const queryNameResolver = () => {
|
|
32151
32872
|
return inject(AppStore).getDefaultQuery()?.name || "";
|
|
32152
32873
|
};
|
|
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,
|
|
32874
|
+
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
32875
|
|
|
32155
32876
|
//# sourceMappingURL=sinequa-atomic-angular.mjs.map
|