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