mirror of https://github.com/Squidex/squidex.git
6 changed files with 231 additions and 0 deletions
@ -0,0 +1,27 @@ |
|||
<div class="nested-field" *ngIf="fieldGroup.separator"> |
|||
<span class="nested-field-line-h"></span> |
|||
|
|||
<sqx-field [field]="$any(fieldGroup.separator)" [languages]="languages" [parent]="parent" [plain]="true" [schema]="schema" [settings]="settings"> |
|||
<div class="d-flex align-items-center"> |
|||
<i cdkDragHandle class="icon-drag2 drag-handle"></i> |
|||
|
|||
<button type="button" class="btn btn-sm btn-text-secondary ms-2" (click)="toggle()"> |
|||
<i [class.icon-caret-right]="snapshot.isCollapsed" [class.icon-caret-down]="!snapshot.isCollapsed"></i> |
|||
</button> |
|||
</div> |
|||
</sqx-field> |
|||
</div> |
|||
|
|||
<div *ngIf="!snapshot.isCollapsed" class="field-group nested-field" [class.field-placeholder]="fieldGroup.fields.length === 0 && !fieldsEmpty" |
|||
cdkDropList |
|||
[cdkDropListDisabled]="!sortable" |
|||
[cdkDropListData]="fieldGroup.fields" |
|||
(cdkDropListDropped)="sorted.emit($event)"> |
|||
<div *ngFor="let field of fieldGroup.fields; trackBy: trackByFieldFn" class="nested-field table-drag" cdkDrag cdkDragLockAxis="y"> |
|||
<span class="nested-field-line-h"></span> |
|||
|
|||
<sqx-field [field]="$any(field)" [languages]="languages" [parent]="parent" [schema]="schema" [settings]="settings"> |
|||
<i cdkDragHandle class="icon-drag2 drag-handle"></i> |
|||
</sqx-field> |
|||
</div> |
|||
</div> |
|||
@ -0,0 +1,16 @@ |
|||
@import 'mixins'; |
|||
@import 'vars'; |
|||
|
|||
$field-line: #c7cfd7; |
|||
|
|||
.field-placeholder { |
|||
border: 2px dashed $field-line; |
|||
border-radius: 0; |
|||
margin-bottom: .25rem; |
|||
margin-top: 0; |
|||
min-height: 4rem; |
|||
} |
|||
|
|||
.cdk-drop-list-dragging { |
|||
border: 0; |
|||
} |
|||
@ -0,0 +1,91 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
|||
*/ |
|||
|
|||
import { CdkDragDrop } from '@angular/cdk/drag-drop'; |
|||
import { ChangeDetectorRef, Component, EventEmitter, Input, Output } from '@angular/core'; |
|||
import { AppSettingsDto, FieldDto, FieldGroup, LanguageDto, LocalStoreService, RootFieldDto, SchemaDto, Settings, StatefulComponent } from '@app/shared'; |
|||
|
|||
interface State { |
|||
// The when the section is collapsed.
|
|||
isCollapsed: boolean; |
|||
} |
|||
|
|||
@Component({ |
|||
selector: 'sqx-field-group[fieldGroup][languages][schema][settings]', |
|||
styleUrls: ['./field-group.component.scss'], |
|||
templateUrl: './field-group.component.html', |
|||
}) |
|||
export class FieldGroupComponent extends StatefulComponent<State> { |
|||
@Output() |
|||
public sorted = new EventEmitter<CdkDragDrop<FieldDto[]>>(); |
|||
|
|||
@Input() |
|||
public languages!: ReadonlyArray<LanguageDto>; |
|||
|
|||
@Input() |
|||
public parent?: RootFieldDto; |
|||
|
|||
@Input() |
|||
public settings!: AppSettingsDto; |
|||
|
|||
@Input() |
|||
public sortable = false; |
|||
|
|||
@Input() |
|||
public schema!: SchemaDto; |
|||
|
|||
@Input() |
|||
public fieldsEmpty = false; |
|||
|
|||
@Input() |
|||
public fieldGroup!: FieldGroup; |
|||
|
|||
public trackByFieldFn: (_index: number, field: FieldDto) => any; |
|||
|
|||
public get hasAnyFields() { |
|||
return this.parent ? this.parent.nested.length > 0 : this.schema.fields.length > 0; |
|||
} |
|||
|
|||
constructor(changeDetector: ChangeDetectorRef, |
|||
private readonly localStore: LocalStoreService, |
|||
) { |
|||
super(changeDetector, { |
|||
isCollapsed: false, |
|||
}); |
|||
|
|||
this.changes.subscribe(state => { |
|||
if (this.fieldGroup?.separator && this.schema) { |
|||
this.localStore.setBoolean(this.isCollapsedKey(), state.isCollapsed); |
|||
} |
|||
}); |
|||
|
|||
this.trackByFieldFn = this.trackByField.bind(this); |
|||
} |
|||
|
|||
public ngOnInit() { |
|||
if (this.fieldGroup?.separator && this.schema) { |
|||
const isCollapsed = this.localStore.getBoolean(this.isCollapsedKey()); |
|||
|
|||
this.next({ isCollapsed }); |
|||
} |
|||
} |
|||
|
|||
public toggle() { |
|||
this.next(s => ({ |
|||
...s, |
|||
isCollapsed: !s.isCollapsed, |
|||
})); |
|||
} |
|||
|
|||
public trackByField(_index: number, field: FieldDto) { |
|||
return field.fieldId + this.schema.id; |
|||
} |
|||
|
|||
private isCollapsedKey(): string { |
|||
return Settings.Local.FIELD_COLLAPSED(this.schema?.id, this.fieldGroup.separator?.fieldId); |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
<div cdkDropListGroup |
|||
cdkDropList |
|||
[cdkDropListDisabled]="!sortable" |
|||
[cdkDropListData]="fieldGroups" |
|||
(cdkDropListDropped)="sortGroups($event)"> |
|||
<div *ngFor="let group of fieldGroups;" class="table-drag" cdkDrag cdkDragLockAxis="y"> |
|||
<sqx-field-group |
|||
[languages]="languages" |
|||
[fieldsEmpty]="fieldsEmpty" |
|||
[fieldGroup]="group" |
|||
[parent]="parent" |
|||
[schema]="schema" |
|||
[settings]="settings" |
|||
[sortable]="sortable" |
|||
(sorted)="sortFields($event)"> |
|||
</sqx-field-group> |
|||
</div> |
|||
</div> |
|||
@ -0,0 +1,79 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
|||
*/ |
|||
|
|||
import { CdkDragDrop, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop'; |
|||
import { Component, EventEmitter, Input, Output } from '@angular/core'; |
|||
import { AppSettingsDto, FieldDto, FieldGroup, groupFields, LanguageDto, RootFieldDto, SchemaDto } from '@app/shared'; |
|||
|
|||
@Component({ |
|||
selector: 'sqx-sortable-field-list[fields][languages][settings]', |
|||
styleUrls: ['./sortable-field-list.component.scss'], |
|||
templateUrl: './sortable-field-list.component.html', |
|||
}) |
|||
export class SortableFieldListComponent { |
|||
@Output() |
|||
public sorted = new EventEmitter<ReadonlyArray<FieldDto>>(); |
|||
|
|||
@Input() |
|||
public languages!: ReadonlyArray<LanguageDto>; |
|||
|
|||
@Input() |
|||
public parent?: RootFieldDto; |
|||
|
|||
@Input() |
|||
public settings!: AppSettingsDto; |
|||
|
|||
@Input() |
|||
public schema!: SchemaDto; |
|||
|
|||
@Input() |
|||
public sortable = false; |
|||
|
|||
@Input() |
|||
public fieldsEmpty = false; |
|||
|
|||
@Input() |
|||
public set fields(value: ReadonlyArray<FieldDto>) { |
|||
this.fieldGroups = groupFields(value, true); |
|||
} |
|||
|
|||
public fieldGroups: FieldGroup[] = []; |
|||
|
|||
public sortGroups(event: CdkDragDrop<FieldGroup[]>) { |
|||
this.onSort(event); |
|||
} |
|||
|
|||
public sortFields(event: CdkDragDrop<FieldDto[]>) { |
|||
this.onSort(event); |
|||
} |
|||
|
|||
private onSort<T>(event: CdkDragDrop<T[]>) { |
|||
if (event.previousContainer === event.container) { |
|||
moveItemInArray(event.container.data, event.previousIndex, event.currentIndex); |
|||
} else { |
|||
transferArrayItem( |
|||
event.previousContainer.data, |
|||
event.container.data, |
|||
event.previousIndex, |
|||
event.currentIndex); |
|||
} |
|||
|
|||
const result: FieldDto[] = []; |
|||
|
|||
for (const group of this.fieldGroups) { |
|||
if (group.separator) { |
|||
result.push(group.separator); |
|||
} |
|||
|
|||
for (const field of group.fields) { |
|||
result.push(field); |
|||
} |
|||
} |
|||
|
|||
this.sorted.emit(result); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue