Browse Source

Menu fix.

pull/1317/head
Sebastian Stehle 2 weeks ago
parent
commit
624b054fa3
  1. 1
      backend/i18n/frontend_de.json
  2. 1
      backend/i18n/frontend_en.json
  3. 1
      backend/i18n/frontend_fr.json
  4. 1
      backend/i18n/frontend_it.json
  5. 1
      backend/i18n/frontend_nl.json
  6. 1
      backend/i18n/frontend_pt.json
  7. 1
      backend/i18n/frontend_zh.json
  8. 1
      backend/i18n/source/frontend_en.json
  9. 4066
      frontend/package-lock.json
  10. 42
      frontend/src/app/features/content/shared/forms/content-field.component.html
  11. 3
      frontend/src/app/features/content/shared/forms/content-field.component.ts
  12. 6
      frontend/src/app/features/content/shared/forms/field-editor.component.html
  13. 5
      frontend/src/app/framework/angular/menu-item.component.html
  14. 31
      frontend/src/app/framework/angular/menu-item.component.ts
  15. 8
      frontend/src/app/framework/angular/menu.component.html
  16. 61
      frontend/src/app/framework/angular/menu.component.ts

1
backend/i18n/frontend_de.json

@ -455,6 +455,7 @@
"contents.changeStatusTo": "Inhaltselement(e) auf {action} ändern",
"contents.changeStatusToImmediately": "Sofort auf {action} setzen.",
"contents.changeStatusToLater": "Zu einem späteren Datum und einer späteren Uhrzeit auf {action} setzen.",
"contents.comment": "Write comment",
"contents.componentInvalid": "Ungültige Komponente, Schema wurde gelöscht.",
"contents.componentNoSchema": "Fügen Sie mindestens ein Schema hinzu, um eine Komponente festzulegen.",
"contents.componentsNoSchema": "Fügen Sie mindestens ein Schema hinzu, um Komponenten hinzuzufügen.",

1
backend/i18n/frontend_en.json

@ -455,6 +455,7 @@
"contents.changeStatusTo": "Change content item(s) to {action}",
"contents.changeStatusToImmediately": "Set to {action} immediately.",
"contents.changeStatusToLater": "Set to {action} at a later point date and time.",
"contents.comment": "Write comment",
"contents.componentInvalid": "Invalid component, schema has been deleted.",
"contents.componentNoSchema": "Add at least one schema to set component.",
"contents.componentsNoSchema": "Add at least one schema to add components.",

1
backend/i18n/frontend_fr.json

@ -455,6 +455,7 @@
"contents.changeStatusTo": "Remplacez le(s) élément(s) de contenu par {action}",
"contents.changeStatusToImmediately": "Réglez sur {action} immédiatement.",
"contents.changeStatusToLater": "Définir sur {action} à une date et une heure ultérieures.",
"contents.comment": "Write comment",
"contents.componentInvalid": "Composant non valide, le schéma a été supprimé.",
"contents.componentNoSchema": "Ajoutez au moins un schéma pour définir le composant.",
"contents.componentsNoSchema": "Ajoutez au moins un schéma pour ajouter des composants.",

1
backend/i18n/frontend_it.json

@ -455,6 +455,7 @@
"contents.changeStatusTo": "Cambia gli elementi del contenuto in {action}",
"contents.changeStatusToImmediately": "Imposta {action} immediatamente.",
"contents.changeStatusToLater": "Imposta {action} ad una data e ora successiva.",
"contents.comment": "Write comment",
"contents.componentInvalid": "Invalid component, schema has been deleted.",
"contents.componentNoSchema": "Add at least one schema to set component.",
"contents.componentsNoSchema": "Add at least one schema to add components.",

1
backend/i18n/frontend_nl.json

@ -455,6 +455,7 @@
"contents.changeStatusTo": "Verander inhoud item(s) in {action}",
"contents.changeStatusToImmediately": "Zet onmiddellijk op {action}.",
"contents.changeStatusToLater": "Zet op {action} op een latere datum en tijd.",
"contents.comment": "Write comment",
"contents.componentInvalid": "Ongeldig component, schema is verwijderd.",
"contents.componentNoSchema": "Voeg ten minste één schema toe om het component te kunnen zetten.",
"contents.componentsNoSchema": "AVoeg ten minste één schema toe om de componenten te kunnen zetten.",

1
backend/i18n/frontend_pt.json

@ -455,6 +455,7 @@
"contents.changeStatusTo": "Alterar o(s) item de conteúdo para {action}",
"contents.changeStatusToImmediately": "Desa um conjunto de {action} imediatamente.",
"contents.changeStatusToLater": "Definir para {action} numa data e hora posteriores.",
"contents.comment": "Write comment",
"contents.componentInvalid": "Componente inválido, o esquema foi eliminado.",
"contents.componentNoSchema": "Adicione pelo menos um esquema para definir o componente.",
"contents.componentsNoSchema": "Adicione pelo menos um esquema para adicionar componentes.",

1
backend/i18n/frontend_zh.json

@ -455,6 +455,7 @@
"contents.changeStatusTo": "将内容项更改为 {action}",
"contents.changeStatusToImmediately": "立即设置为 {action}。",
"contents.changeStatusToLater": "在稍后的日期和时间设置为 {action}。",
"contents.comment": "Write comment",
"contents.componentInvalid": "Invalid component, schema has been deleted.",
"contents.componentNoSchema": "添加至少一个Schemas来设置组件。",
"contents.componentsNoSchema": "添加至少一个Schemas来添加组件。",

1
backend/i18n/source/frontend_en.json

@ -455,6 +455,7 @@
"contents.changeStatusTo": "Change content item(s) to {action}",
"contents.changeStatusToImmediately": "Set to {action} immediately.",
"contents.changeStatusToLater": "Set to {action} at a later point date and time.",
"contents.comment": "Write comment",
"contents.componentInvalid": "Invalid component, schema has been deleted.",
"contents.componentNoSchema": "Add at least one schema to set component.",
"contents.componentsNoSchema": "Add at least one schema to add components.",

4066
frontend/package-lock.json

File diff suppressed because it is too large

42
frontend/src/app/features/content/shared/forms/content-field.component.html

@ -103,30 +103,32 @@
</div>
<ng-template #sharedButtons>
<sqx-field-languages
[formModel]="formModel"
[language]="language"
(languageChange)="languageChange.emit($event)"
[languages]="languages"
[showAllControls]="showAllControls"
(showAllControlsChange)="changeShowAllControls($event)" />
<fieldset class="d-flex gap-1" [disabled]="(isDisabled | async) || isCollapsed">
<sqx-field-languages
[formModel]="formModel"
[language]="language"
(languageChange)="languageChange.emit($event)"
[languages]="languages"
[showAllControls]="showAllControls"
(showAllControlsChange)="changeShowAllControls($event)" />
<sqx-field-copy-button [formModel]="formModel" [languages]="languages" />
<sqx-field-copy-button [formModel]="formModel" [languages]="languages" />
@if (isTranslatable) {
<sqx-menu-item
(action)="translate()"
[disabled]="formModel.field.isDisabled"
icon="translate"
menuLabel="i18n:contents.autotranslateMenu"
small
tabIndex="-1"
tooltip="i18n:contents.autotranslate" />
}
@if (isTranslatable) {
<sqx-menu-item
(action)="translate()"
[disabled]="formModel.field.isDisabled"
icon="translate"
menuLabel="i18n:contents.autotranslateMenu"
small
tabIndex="-1"
tooltip="i18n:contents.autotranslate" />
}
</fieldset>
@if (isCollapsed) {
<sqx-menu-item (action)="toggle()" icon="minus2" menuLabel="i18n:common.collapse" small tooltip="i18n:contents.arrayCollapseItem" />
<sqx-menu-item (action)="toggle()" icon="angle-down" menuLabel="i18n:common.collapse" small tooltip="i18n:contents.arrayCollapseItem" />
} @else {
<sqx-menu-item (action)="toggle()" icon="plus2" menuLabel="i18n:common.expand" small tooltip="i18n:contents.arrayCollapseItem" />
<sqx-menu-item (action)="toggle()" icon="angle-up" menuLabel="i18n:common.expand" small tooltip="i18n:contents.arrayCollapseItem" />
}
</ng-template>

3
frontend/src/app/features/content/shared/forms/content-field.component.ts

@ -8,7 +8,7 @@
import { AsyncPipe, NgTemplateOutlet } from '@angular/common';
import { booleanAttribute, Component, EventEmitter, HostBinding, inject, Input, numberAttribute, Optional, Output } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { AppLanguageDto, AppsState, changed$, CommentSelected, CommentsState, disabled$, EditContentForm, FieldForm, FocusMarkerComponent, invalid$, LocalStoreService, MenuItemComponent, MessageBus, SchemaDto, Settings, Subscriptions, TranslateDto, TranslatePipe, TranslationsService, TypedSimpleChanges, UIOptions } from '@app/shared';
import { AppLanguageDto, AppsState, changed$, CommentSelected, CommentsState, disabled$, EditContentForm, FieldForm, FocusMarkerComponent, invalid$, LocalStoreService, MenuItemComponent, MenuItemRegistry, MessageBus, SchemaDto, Settings, Subscriptions, TranslateDto, TranslatePipe, TranslationsService, TypedSimpleChanges, UIOptions } from '@app/shared';
import { FieldCopyButtonComponent } from './field-copy-button.component';
import { FieldEditorComponent } from './field-editor.component';
import { FieldLanguagesComponent } from './field-languages.component';
@ -27,6 +27,7 @@ import { FieldLanguagesComponent } from './field-languages.component';
NgTemplateOutlet,
TranslatePipe,
],
providers: [MenuItemRegistry],
})
export class ContentFieldComponent {
private readonly subscriptions = new Subscriptions();

6
frontend/src/app/features/content/shared/forms/field-editor.component.html

@ -1,5 +1,5 @@
@if (formModel) {
<div class="field" (click)="select()" [class.expanded]="isExpanded && !isCollapsed" #root>
<div class="field" #root [class.expanded]="isExpanded && !isCollapsed" (click)="select()">
<div class="row">
<div class="col-4">
<div class="truncate">
@ -14,9 +14,7 @@
<div class="col-8 buttons">
<sqx-menu alignment="right" small>
<fieldset class="d-flex gap-1" [disabled]="(isDisabled | async) || isCollapsed">
<ng-content></ng-content>
</fieldset>
<ng-content></ng-content>
@if (isString) {
<sqx-menu-item

5
frontend/src/app/framework/angular/menu-item.component.html

@ -1,15 +1,14 @@
<ng-template #dropdownTemplate>
<button
class="dropdown-item"
[attr.disabled]="disabled"
[class.disabled]="disabled"
[disabled]="disabled"
[confirmRememberKey]="confirmRememberKey"
[confirmText]="confirmText | sqxTranslate"
[confirmTitle]="confirmTitle | sqxTranslate"
(sqxConfirmClick)="action.emit()"
[title]="tooltip | sqxTranslate"
type="button">
{{ menuLabel || label | sqxTranslate }}
{{ actualMenuLabel }}
</button>
</ng-template>

31
frontend/src/app/framework/angular/menu-item.component.ts

@ -6,8 +6,10 @@
*/
import { booleanAttribute, ChangeDetectionStrategy, Component, EventEmitter, Input, numberAttribute, Output, TemplateRef, ViewChild } from '@angular/core';
import { booleanAttribute, ChangeDetectionStrategy, Component, EventEmitter, Input, numberAttribute, Optional, Output, TemplateRef, ViewChild } from '@angular/core';
import { LocalizerService, TypedSimpleChanges } from '../internal';
import { ConfirmClickDirective } from './forms/confirm-click.directive';
import { MenuItemRegistry } from './menu.component';
import { TooltipDirective } from './modals/tooltip.directive';
import { TranslatePipe } from './pipes/translate.pipe';
@ -59,6 +61,33 @@ export class MenuItemComponent {
@ViewChild('dropdownTemplate', { static: true })
template!: TemplateRef<any>;
public actualMenuLabel = '';
constructor(
@Optional() private readonly menuItemRegistry: MenuItemRegistry | null,
private readonly localizerService: LocalizerService,
) {
}
public ngOnInit() {
this.menuItemRegistry?.registerItem(this);
}
public ngOnDestroy() {
this.menuItemRegistry?.unregisterItem(this);
}
public ngOnChanges(changes: TypedSimpleChanges<MenuItemComponent>) {
if (changes.label || changes.menuLabel) {
const key = this.menuLabel || this.label;
if (key) {
this.actualMenuLabel = this.localizerService.getOrKey(key);
} else {
this.actualMenuLabel = '';
}
}
}
public get showInDropdown() {
return this.label || this.menuLabel;
}

8
frontend/src/app/framework/angular/menu.component.html

@ -1,5 +1,5 @@
<div class="d-flex flex-nowrap relative" #container [class.gap-1]="small" [class.gap-2]="!small" [class.justify-content-end]="isRightAligned">
@if (overflowMenuItems) {
@if (overflowMenuItems(); as menu) {
<button
class="btn btn-text-secondary"
#buttonOptions
@ -17,7 +17,7 @@
scrollY="true"
[sqxAnchoredTo]="buttonOptions"
*sqxModal="overflowDropdown; closeAlways: true">
@for (item of overflowMenuItems; track item) {
@for (item of menu; track item) {
<ng-container *ngTemplateOutlet="item.template"></ng-container>
}
</sqx-dropdown-menu>
@ -28,8 +28,8 @@
#menu
[class.gap-1]="small"
[class.gap-2]="!small"
[class.overflown-left]="!!overflowMenuItems && !isRightAligned"
[class.overflown-right]="!!overflowMenuItems && isRightAligned">
[class.overflown-left]="!!overflowMenuItems() && !isRightAligned"
[class.overflown-right]="!!overflowMenuItems() && isRightAligned">
<ng-content />
</div>
</div>

61
frontend/src/app/framework/angular/menu.component.ts

@ -7,7 +7,7 @@
import { NgTemplateOutlet } from '@angular/common';
import { AfterViewInit, booleanAttribute, ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChildren, ElementRef, Input, QueryList, ViewChild } from '@angular/core';
import { AfterViewInit, booleanAttribute, ChangeDetectionStrategy, Component, computed, ElementRef, Injectable, Input, Optional, signal, SkipSelf, ViewChild } from '@angular/core';
import { ModalModel, ResizeListener, ResizeService, Subscriptions } from '@app/framework/internal';
import { DropdownMenuComponent } from './dropdown-menu.component';
import { MenuItemComponent } from './menu-item.component';
@ -15,6 +15,27 @@ import { ModalPlacementDirective } from './modals/modal-placement.directive';
import { ModalDirective } from './modals/modal.directive';
import { TranslatePipe } from './pipes/translate.pipe';
@Injectable()
export class MenuItemRegistry {
public readonly menuItems = signal(new Set<MenuItemComponent>());
public registerItem(item: MenuItemComponent) {
this.menuItems.update(x => {
const update = new Set<MenuItemComponent>(x);
update.add(item);
return update;
});
}
public unregisterItem(item: MenuItemComponent) {
this.menuItems.update(x => {
const update = new Set<MenuItemComponent>(x);
update.delete(item);
return update;
});
}
}
@Component({
selector: 'sqx-menu',
styleUrls: ['./menu.component.scss'],
@ -27,11 +48,17 @@ import { TranslatePipe } from './pipes/translate.pipe';
NgTemplateOutlet,
TranslatePipe,
],
providers: [{
provide: MenuItemRegistry,
useFactory: (menu: MenuComponent) => menu.menuItemsRegistry,
deps: [MenuComponent],
}]
})
export class MenuComponent implements AfterViewInit, ResizeListener {
private readonly subscriptions = new Subscriptions();
private measuredContainer?: number;
private measuredMenu?: number;
private readonly menuItemsRegistry: MenuItemRegistry;
private readonly measuredContainer = signal(-1);
private readonly measuredMenu = signal(-1);
@Input()
public alignment: 'left' | 'right' = 'left';
@ -48,10 +75,6 @@ export class MenuComponent implements AfterViewInit, ResizeListener {
@ViewChild('menu', { static: true })
public menu!: ElementRef<HTMLDivElement>;
@ContentChildren(MenuItemComponent)
public menuItems!: QueryList<MenuItemComponent>;
public overflowMenuItems: MenuItemComponent[] | null = null;
public overflowDropdown = new ModalModel();
public get isRightAligned() {
@ -60,8 +83,9 @@ export class MenuComponent implements AfterViewInit, ResizeListener {
constructor(
private readonly resizeService: ResizeService,
private readonly changeDetector: ChangeDetectorRef,
@Optional() @SkipSelf() parentMenuItemRegistry?: MenuItemRegistry,
) {
this.menuItemsRegistry = parentMenuItemRegistry ?? new MenuItemRegistry();
}
public ngAfterViewInit() {
@ -71,17 +95,22 @@ export class MenuComponent implements AfterViewInit, ResizeListener {
public onResize(rect: DOMRect, element: Element): void {
if (element === this.container.nativeElement) {
this.measuredContainer = rect.width;
this.measuredContainer.set(rect.width);
} else {
this.measuredMenu = Math.max(rect.width, element.scrollWidth);
this.measuredMenu.set(Math.max(rect.width, element.scrollWidth));
}
}
if (!this.measuredContainer || !this.measuredMenu) {
return;
protected overflowMenuItems = computed(() => {
const items = this.menuItemsRegistry.menuItems();
const measuredContainer = this.measuredContainer();
const measuredMenu = this.measuredMenu();
if (measuredContainer < 0 || measuredMenu < 0) {
return null;
}
const isOverlapping = this.measuredMenu > this.measuredContainer;
this.overflowMenuItems = isOverlapping ? this.menuItems.toArray().filter(x => x.showInDropdown) : null;
this.changeDetector.detectChanges();
}
const isOverlapping = measuredMenu > measuredContainer;
return isOverlapping ? [...items.values()].filter(x => x.actualMenuLabel).sortedByString(x => x.actualMenuLabel) : null;
});
}
Loading…
Cancel
Save