Browse Source

Change category name (#948)

* Typings for editor SDK.

* Fix line height and cut text.

* Improve integrated documentation for .NET SDK

* Add access token to preview URL.

* Change category name.

* Fix tests.
pull/949/head
Sebastian Stehle 4 years ago
committed by GitHub
parent
commit
b607c86062
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 13
      frontend/src/app/features/rules/pages/rules/rule.component.html
  2. 10
      frontend/src/app/features/settings/pages/clients/client.component.html
  3. 7
      frontend/src/app/features/settings/pages/workflows/workflow-step.component.html
  4. 11
      frontend/src/app/framework/angular/avatar.stories.tsx
  5. 63
      frontend/src/app/framework/angular/forms/editable-title.component.html
  6. 46
      frontend/src/app/framework/angular/forms/editable-title.component.scss
  7. 51
      frontend/src/app/framework/angular/forms/editable-title.component.ts
  8. 134
      frontend/src/app/framework/angular/forms/editable-title.stories.ts
  9. 15
      frontend/src/app/shared/components/schema-category.component.html
  10. 19
      frontend/src/app/shared/components/schema-category.component.scss
  11. 6
      frontend/src/app/shared/components/schema-category.component.ts
  12. 12
      frontend/src/app/shared/state/schemas.state.spec.ts
  13. 27
      frontend/src/app/shared/state/schemas.state.ts

13
frontend/src/app/features/rules/pages/rules/rule.component.html

@ -2,12 +2,13 @@
<div class="card-header">
<div class="row">
<div class="col col-name">
<sqx-editable-title [disabled]="!rule.canUpdate"
[fallback]="'rules.unnamed' | sqxTranslate"
[name]="rule.name"
(nameChange)="rename($event)"
[maxLength]="60"
[isRequired]="false">
<sqx-editable-title
[disabled]="!rule.canUpdate"
[displayFallback]="'rules.unnamed' | sqxTranslate"
(inputTitleChange)="rename($event)"
[inputTitle]="rule.name"
[inputTitleLength]="60"
[inputTitleRequired]="false" >
</sqx-editable-title>
</div>
<div class="col-auto" [class.invisible]="!rule.canDelete && !rule.canRun">

10
frontend/src/app/features/settings/pages/clients/client.component.html

@ -1,8 +1,12 @@
<div class="card">
<div class="card-header">
<div class="row g-0">
<div class="row g-1">
<div class="col col-name">
<sqx-editable-title [name]="client.name" (nameChange)="rename($event)" [disabled]="!client.canUpdate">
<sqx-editable-title
[disabled]="!client.canUpdate"
(inputTitleChange)="rename($event)"
[inputTitle]="client.name"
[inputTitleLength]="100">
</sqx-editable-title>
</div>
<div class="col-auto">
@ -10,7 +14,7 @@
{{ 'clients.connect' | sqxTranslate }}
</button>
</div>
<div class="col-auto cell-actions">
<div class="col-auto">
<button type="button" class="btn btn-text-danger" [disabled]="!client.canRevoke"
(sqxConfirmClick)="revoke()"
confirmTitle="i18n:clients.deleteConfirmTitle"

7
frontend/src/app/features/settings/pages/workflows/workflow-step.component.html

@ -18,9 +18,10 @@
</div>
<div class="col">
<sqx-editable-title
[name]="step.name"
(nameChange)="changeName($event)"
[disabled]="step.isLocked || disabled">
[disabled]="step.isLocked || disabled"
(inputTitleChange)="changeName($event)"
[inputTitle]="step.name"
[inputTitleLength]="100">
</sqx-editable-title>
</div>
<div class="col">

11
frontend/src/app/framework/angular/avatar.stories.tsx

@ -5,8 +5,9 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { moduleMetadata } from '@storybook/angular';
import { Meta, Story } from '@storybook/angular/types-6-0';
import { AvatarComponent } from '@app/framework';
import { AvatarComponent, SqxFrameworkModule } from '@app/framework';
export default {
title: 'Framework/Avatar',
@ -22,6 +23,14 @@ export default {
control: 'number',
},
},
decorators: [
moduleMetadata({
imports: [
SqxFrameworkModule,
SqxFrameworkModule.forRoot(),
],
}),
],
} as Meta;
const Template: Story<AvatarComponent> = (args: AvatarComponent) => ({

63
frontend/src/app/framework/angular/forms/editable-title.component.html

@ -1,32 +1,41 @@
<div class="title">
<form *ngIf="renaming; else noRenaming" (ngSubmit)="rename()">
<div class="row g-0">
<div class="col">
<div class="form-group me-2">
<sqx-control-errors for="name"></sqx-control-errors>
<input class="form-control" [formControl]="renameForm" [maxLength]="maxLength" sqxFocusOnInit (keydown)="onKeyDown($event)" spellcheck="false">
</div>
</div>
<div class="col-auto">
<button type="submit" class="btn btn-primary me-1" [disabled]="!renameForm.valid || !renameForm.dirty">
{{ 'common.save' | sqxTranslate }}
</button>
<button type="button" class="btn btn-text-secondary btn-cancel me-4" (click)="toggleRename()">
<i class="icon-close"></i>
</button>
<form *ngIf="renaming; else noRenaming" (ngSubmit)="rename()">
<div class="row g-1 align-items-center">
<div class="col">
<div class="form-group">
<sqx-control-errors [for]="renameForm"></sqx-control-errors>
<input class="form-control form-control-{{size}}" [formControl]="renameForm" [maxLength]="inputTitleLength" sqxFocusOnInit (keydown)="onKeyDown($event)" spellcheck="false">
</div>
</div>
</form>
<div class="col-auto">
<button type="submit" class="btn btn-primary btn-{{size}}" [disabled]="!renameForm.valid || !renameForm.dirty">
<i class="icon-checkmark"></i>
</button>
</div>
<div class="col-auto" *ngIf="closeButton">
<button type="button" class="btn btn-text-secondary btn-{{size}} btn-cancel" (click)="toggleRename()">
<i class="icon-close"></i>
</button>
</div>
</div>
</form>
<ng-template #noRenaming>
<div class="title-view">
<h3 class="title-name" [class.fallback]="!name" (dblclick)="toggleRename()">
{{name || fallback}}
<ng-template #noRenaming>
<div class="row g-0 align-items-center title-view d-nowrap">
<div class="col">
<h3 class="truncate {{size}}" [class.fallback]="!inputTitle" (dblclick)="toggleRename()">
{{inputTitle || displayFallback}}
</h3>
<i class="title-edit icon-pencil" *ngIf="!disabled" (click)="toggleRename()"></i>
</div>
</ng-template>
</div>
<div class="col-auto title-edit" *ngIf="!disabled" >
<button type="button" class="btn btn-text-secondary btn-{{size}}" (click)="toggleRename()">
<i class="icon-pencil text-decent"></i>
</button>
</div>
<div class="col-auto">
<button type="button" class="btn btn-text-secondary btn-{{size}} btn-placeholder" (click)="toggleRename()">
&nbsp;
</button>
</div>
</div>
</ng-template>

46
frontend/src/app/framework/angular/forms/editable-title.component.scss

@ -1,34 +1,36 @@
@import 'mixins';
@import 'vars';
.title {
@include hover-visible('.title-edit', inline);
position: relative;
&-edit {
@include absolute(0, 0, null, null);
color: darken($color-border-dark, 20%);
cursor: pointer;
padding: .6rem .25rem;
}
:host {
display: block;
}
&-name {
display: inline;
font-size: 1.2rem;
font-weight: normal;
padding-right: 1.75rem;
}
.title-view {
@include hover-visible('.title-edit', block);
&-view {
@include truncate;
border-bottom: 1px solid transparent;
border-top: 0;
padding: .375rem 0;
position: absolute;
.col {
overflow: hidden;
}
}
.btn-placeholder {
padding-left: 0;
padding-right: 0;
width: 0;
}
.row {
flex-wrap: nowrap;
}
h3 {
margin: 0;
&.sm {
font-size: 1rem;
font-weight: normal;
}
&.fallback {
color: $color-text-decent;
}

51
frontend/src/app/framework/angular/forms/editable-title.component.ts

@ -6,42 +6,41 @@
*/
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { UntypedFormControl, Validators } from '@angular/forms';
import { FormControl, ValidatorFn, Validators } from '@angular/forms';
import { Keys } from '@app/framework/internal';
@Component({
selector: 'sqx-editable-title[name]',
selector: 'sqx-editable-title[inputTitle]',
styleUrls: ['./editable-title.component.scss'],
templateUrl: './editable-title.component.html',
})
export class EditableTitleComponent {
@Output()
public nameChange = new EventEmitter<string>();
public inputTitleChange = new EventEmitter<string>();
@Input()
public disabled?: boolean | null;
public inputTitle!: string;
@Input()
public fallback = '';
public inputTitleLength = 20;
@Input()
public name!: string;
public inputTitleRequired = true;
@Input()
public maxLength = 20;
public disabled?: boolean | null;
@Input()
public set isRequired(value: boolean) {
const validator =
value ?
Validators.required :
Validators.nullValidator;
public closeButton = true;
this.renameForm.setValidators(validator);
}
@Input()
public size: 'sm' | 'md' | 'lg' = 'md';
@Input()
public displayFallback = '';
public renaming = false;
public renameForm = new UntypedFormControl();
public renameForm = new FormControl<string>('');
public onKeyDown(event: KeyboardEvent) {
if (Keys.isEscape(event)) {
@ -54,7 +53,21 @@ export class EditableTitleComponent {
return;
}
this.renameForm.setValue(this.name || '');
if (!this.renaming) {
let validators: ValidatorFn[] = [];
if (this.inputTitleLength) {
validators.push(Validators.maxLength(this.inputTitleLength));
}
if (this.inputTitleRequired) {
validators.push(Validators.required);
}
this.renameForm.setValidators(validators);
}
this.renameForm.setValue(this.inputTitle || '');
this.renaming = !this.renaming;
}
@ -64,10 +77,10 @@ export class EditableTitleComponent {
}
if (this.renameForm.valid) {
const name = this.renameForm.value;
const text = this.renameForm.value || '';
this.nameChange.emit(name);
this.name = name;
this.inputTitleChange.emit(text);
this.inputTitle = text;
this.renaming = false;
}

134
frontend/src/app/framework/angular/forms/editable-title.stories.ts

@ -0,0 +1,134 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { moduleMetadata } from '@storybook/angular';
import { Meta, Story } from '@storybook/angular/types-6-0';
import { EditableTitleComponent, LocalizerService, SqxFrameworkModule } from '@app/framework';
export default {
title: 'Framework/EditableTitle',
component: EditableTitleComponent,
argTypes: {
inputTitle: {
control: 'inputTitle',
},
closeButton: {
control: 'boolean',
},
inputTitleRequired: {
control: 'boolean',
},
inputTitleLength: {
control: 'number',
},
size: {
control: 'select',
options: [
'sm',
'md',
'lg',
],
},
},
args: {
closeButton: true,
inputTitleLength: 30,
inputTitleRequired: true,
},
decorators: [
moduleMetadata({
imports: [
BrowserAnimationsModule,
FormsModule,
ReactiveFormsModule,
SqxFrameworkModule,
SqxFrameworkModule.forRoot(),
],
providers: [
{ provide: LocalizerService, useValue: new LocalizerService({}) },
],
}),
],
} as Meta;
const Template: Story<EditableTitleComponent> = (args: EditableTitleComponent) => ({
props: args,
template: `
<div class="card mt-4">
<div class="row" style="flex-wrap: nowrap">
<div class="col-9">
<sqx-editable-title
[closeButton]="closeButton"
[size]="size"
[inputTitle]="inputTitle"
[inputTitleLength]="inputTitleLength"
[inputTitleRequired]="inputTitleRequired">
</sqx-editable-title>
</div>
<div class="col-3">
<button class="btn btn-primary btn-{{size}}">
Button
</button>
</div>
</div>
</div>
`,
});
export const Default = Template.bind({});
Default.args = {
inputTitle: 'My Title',
size: 'md',
};
export const DefaultNoCloseButton = Template.bind({});
DefaultNoCloseButton.args = {
inputTitle: 'My Title',
size: 'md',
closeButton: false,
};
export const Small = Template.bind({});
Small.args = {
inputTitle: 'My Title',
size: 'sm',
};
export const SmallNoCloseButton = Template.bind({});
SmallNoCloseButton.args = {
inputTitle: 'My Title',
size: 'sm',
closeButton: false,
};
export const Large = Template.bind({});
Large.args = {
inputTitle: 'My Title',
size: 'lg',
};
export const LargeNoCloseButton = Template.bind({});
LargeNoCloseButton.args = {
inputTitle: 'My Title',
size: 'lg',
closeButton: false,
};
export const LongTitle = Template.bind({});
LongTitle.args = {
inputTitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua',
size: 'md',
};

15
frontend/src/app/shared/components/schema-category.component.html

@ -6,16 +6,21 @@
(cdkDropListDropped)="changeCategory($event)">
<li class="nav-item nav-heading">
<div class="row g-0 align-items-center mb-1">
<div class="row g-1 align-items-center">
<div class="col-auto">
<button type="button" class="btn btn-sm btn-decent btn-text-secondary btn-toggle" (click)="toggle()">
<i [class.icon-caret-right]="isCollapsed" [class.icon-caret-down]="!isCollapsed"></i>
</button>
</div>
<div class="col">
<div class="truncate">
{{schemaCategory.displayName | sqxTranslate}}
</div>
<div class="col pe-1">
<sqx-editable-title
size="sm"
[closeButton]="false"
(inputTitleChange)="changeName($event)"
[inputTitle]="schemaCategory.displayName | sqxTranslate"
[inputTitleLength]="30"
[disabled]="!schemaCategory.name">
</sqx-editable-title>
</div>
<div class="col-auto">
<ng-container *ngIf="schemaCategory.countSchemasInSubtree > 0; else noSchemas">

19
frontend/src/app/shared/components/schema-category.component.scss

@ -3,10 +3,6 @@
$drag-margin: -8px;
.col {
overflow: hidden;
}
.btn {
width: 2rem;
@ -59,6 +55,7 @@ $drag-margin: -8px;
.nav-heading {
margin-left: -1rem;
margin-top: 0;
max-width: none;
}
.nav-collapsed {
@ -69,6 +66,14 @@ $drag-margin: -8px;
margin-top: 1rem;
}
.nav-category {
max-width: 100%;
}
.nav-panel {
max-width: 100%;
}
.nav-item {
align-items: center;
@ -81,12 +86,6 @@ $drag-margin: -8px;
margin-bottom: 1px;
}
.nav-category,
.nav-panel,
sqx-schema-category {
max-width: 100%;
}
.categories {
padding: 0;
padding-left: 1rem;

6
frontend/src/app/shared/components/schema-category.component.ts

@ -65,6 +65,12 @@ export class SchemaCategoryComponent implements OnChanges {
}
}
public changeName(name: string) {
if (name !== this.schemaCategory.displayName) {
this.schemasState.renameCategory(this.schemaCategory.displayName, name);
}
}
public changeCategory(drag: CdkDragDrop<any>) {
if (drag.previousContainer !== drag.container) {
this.schemasState.changeCategory(drag.item.data, this.schemaCategory.name);

12
frontend/src/app/shared/state/schemas.state.spec.ts

@ -474,6 +474,18 @@ describe('SchemasState', () => {
expect(schemasState.snapshot.schemas).toEqual([updated, schema2]);
expect(schemasState.snapshot.selectedSchema).toEqual(updated);
});
it('should update schema with matching category', () => {
const updated = createSchema(1, '_new');
schemasService.setup(x => x.putCategory(app, schema1, { name: 'new-name' }, version))
.returns(() => of(updated)).verifiable();
schemasState.renameCategory('schema-category1', 'new-name').subscribe();
expect(schemasState.snapshot.schemas).toEqual([updated, schema2]);
expect(schemasState.snapshot.selectedSchema).toEqual(updated);
});
});
});

27
frontend/src/app/shared/state/schemas.state.ts

@ -6,7 +6,7 @@
*/
import { Injectable } from '@angular/core';
import { EMPTY, Observable, of } from 'rxjs';
import { EMPTY, forkJoin, Observable, of } from 'rxjs';
import { catchError, finalize, tap } from 'rxjs/operators';
import { DialogService, LoadingState, shareMapSubscribed, shareSubscribed, State, Version } from '@app/framework';
import { AddFieldDto, CreateSchemaDto, FieldDto, FieldRule, NestedFieldDto, RootFieldDto, SchemaDto, SchemasService, UpdateFieldDto, UpdateSchemaDto, UpdateUIFields } from './../services/schemas.service';
@ -186,6 +186,31 @@ export class SchemasState extends State<Snapshot> {
}, 'Category Removed');
}
public renameCategory(oldName: string, name: string) {
const schemas = this.snapshot.schemas.filter(x => x.category === oldName);
return forkJoin(schemas.map(s => this.schemasService.putCategory(this.appName, s, { name }, s.version))).pipe(
tap(updated => {
this.next(s => {
let { schemas, selectedSchema } = s;
for (const schema of updated) {
schemas = schemas.replacedBy('id', schema).sortedByString(x => x.displayName);
selectedSchema =
schema &&
selectedSchema &&
selectedSchema.id === schema.id ?
schema :
selectedSchema;
}
return { ...s, schemas, selectedSchema };
}, 'Updated');
}),
shareSubscribed(this.dialogs));
}
public publish(schema: SchemaDto): Observable<SchemaDto> {
return this.schemasService.publishSchema(this.appName, schema, schema.version).pipe(
tap(updated => {

Loading…
Cancel
Save