Browse Source

Fix for panels.

pull/372/head
Sebastian Stehle 7 years ago
parent
commit
d4fccf1801
  1. 2
      src/Squidex/app-config/karma-test-shim.js
  2. 2
      src/Squidex/app/features/settings/declarations.ts
  3. 12
      src/Squidex/app/features/settings/module.ts
  4. 63
      src/Squidex/app/features/settings/pages/workflows/workflow-step.component.html
  5. 39
      src/Squidex/app/features/settings/pages/workflows/workflow-step.component.scss
  6. 69
      src/Squidex/app/features/settings/pages/workflows/workflow-step.component.ts
  7. 37
      src/Squidex/app/features/settings/pages/workflows/workflows-page.component.html
  8. 2
      src/Squidex/app/features/settings/pages/workflows/workflows-page.component.scss
  9. 65
      src/Squidex/app/features/settings/pages/workflows/workflows-page.component.ts
  10. 6
      src/Squidex/app/features/settings/settings-area.component.html
  11. 6
      src/Squidex/app/framework/angular/panel-container.directive.ts
  12. 14
      src/Squidex/app/framework/angular/panel.component.ts
  13. 1
      src/Squidex/app/shared/internal.ts
  14. 188
      src/Squidex/app/shared/services/workflows.service.spec.ts
  15. 121
      src/Squidex/app/shared/services/workflows.service.ts
  16. 4
      src/Squidex/package-lock.json

2
src/Squidex/app-config/karma-test-shim.js

@ -18,7 +18,7 @@ testing.TestBed.initTestEnvironment(
browser.platformBrowserDynamicTesting()
);
var testContext = require.context('../app', true, /workflows\.service\.spec\.ts/);
var testContext = require.context('../app', true, /\.spec\.ts/);
/**
* Get all the files, for each file, call the context function

2
src/Squidex/app/features/settings/declarations.ts

@ -18,7 +18,5 @@ export * from './pages/patterns/patterns-page.component';
export * from './pages/plans/plans-page.component';
export * from './pages/roles/role.component';
export * from './pages/roles/roles-page.component';
export * from './pages/workflows/workflow-step.component';
export * from './pages/workflows/workflows-page.component';
export * from './settings-area.component';

12
src/Squidex/app/features/settings/module.ts

@ -30,9 +30,7 @@ import {
PlansPageComponent,
RoleComponent,
RolesPageComponent,
SettingsAreaComponent,
WorkflowsPageComponent,
WorkflowStepComponent
SettingsAreaComponent
} from './declarations';
const routes: Routes = [
@ -172,10 +170,6 @@ const routes: Routes = [
}
}
]
},
{
path: 'workflows',
component: WorkflowsPageComponent
}
]
}
@ -202,9 +196,7 @@ const routes: Routes = [
PlansPageComponent,
RoleComponent,
RolesPageComponent,
SettingsAreaComponent,
WorkflowsPageComponent,
WorkflowStepComponent
SettingsAreaComponent
]
})
export class SqxFeatureSettingsModule { }

63
src/Squidex/app/features/settings/pages/workflows/workflow-step.component.html

@ -1,63 +0,0 @@
<div class="step">
<div class="row no-gutters">
<div class="col-auto color pr-2">
<sqx-color-picker mode="Circle"
[ngModelOptions]="onBlur"
[ngModel]="step.color"
(ngModelChange)="changeColor($event)"
[disabled]="step.isLocked">
</sqx-color-picker>
</div>
<div class="col">
<sqx-editable-title
[name]="step.name"
(nameChanged)="changeName($event)"
[disabled]="step.isLocked">
</sqx-editable-title>
</div>
<div class="col-auto">
<button type="button" class="btn btn-text-danger" (click)="remove.emit()" *ngIf="!step.isLocked">
<i class="icon-bin2"></i>
</button>
</div>
</div>
<div class="row transition no-gutters" *ngFor="let transition of transitions">
<div class="col-auto">
<i class="icon-arrow-right text-decent"></i>
</div>
<div class="col-3 pl-2">
<div class="transition-to">
<div class="color-circle" [style.background]="transition.step.color"></div> {{transition.to}}
</div>
</div>
<div class="col pl-2">
<button class="btn btn-block btn-outline-secondary dashed">
Add Expression
</button>
</div>
<div class="col-auto pl-2">
<button type="button" class="btn btn-text-danger" (click)="transitionRemove.emit(transition)">
<i class="icon-bin2"></i>
</button>
</div>
</div>
<div class="row transition no-gutters" *ngIf="openSteps.length > 0">
<div class="col-auto">
<i class="icon-arrow-right text-decent"></i>
</div>
<div class="col-3 pl-2">
<sqx-dropdown [items]="openSteps" [(ngModel)]="openStep">
<ng-template let-target="$implicit">
<div class="color-circle" [style.background]="target.color"></div> {{target.name}}
</ng-template>
</sqx-dropdown>
</div>
<div class="col pl-2">
<button class="btn btn-outline-secondary" (click)="transitionAdd.emit(openStep)">
<i class="icon-plus"></i>
</button>
</div>
</div>
</div>

39
src/Squidex/app/features/settings/pages/workflows/workflow-step.component.scss

@ -1,39 +0,0 @@
@import '_vars';
@import '_mixins';
.color {
line-height: 2.8rem;
}
.color-circle {
@include circle(12px);
border: 1px solid $color-border-dark;
background: $color-border;
display: inline-block;
}
.dashed {
border-style: dashed;
}
.transition {
& {
padding-left: 1rem;
margin-top: .25rem;
margin-bottom: .5rem;
line-height: 2rem;
}
&-to {
padding: .5rem .75rem;
background: transparent;
border: 1px solid transparent;
line-height: 1.2rem;
}
}
.step {
& {
margin-bottom: 1rem;
}
}

69
src/Squidex/app/features/settings/pages/workflows/workflow-step.component.ts

@ -1,69 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import {
WorkflowDto,
WorkflowStep,
WorkflowStepValues,
WorkflowTransition,
WorkflowTransitionView
} from '@app/shared';
@Component({
selector: 'sqx-workflow-step',
styleUrls: ['./workflow-step.component.scss'],
templateUrl: './workflow-step.component.html'
})
export class WorkflowStepComponent implements OnChanges {
@Input()
public workflow: WorkflowDto;
@Input()
public step: WorkflowStep;
@Output()
public transitionAdd = new EventEmitter<WorkflowStep>();
@Output()
public transitionRemove = new EventEmitter<WorkflowTransition>();
@Output()
public update = new EventEmitter<WorkflowStepValues>();
@Output()
public rename = new EventEmitter<string>();
@Output()
public remove = new EventEmitter();
public onBlur = { updateOn: 'blur' };
public openSteps: WorkflowStep[];
public openStep: WorkflowStep;
public transitions: WorkflowTransitionView[];
public ngOnChanges(changes: SimpleChanges) {
if (changes['workflow'] || changes['step'] || false) {
this.openSteps = this.workflow.getOpenSteps(this.step);
this.openStep = this.openSteps[0];
this.transitions = this.workflow.getTransitions(this.step);
}
}
public changeName(name: string) {
this.rename.emit(name);
}
public changeColor(color: string) {
this.update.emit({ color });
}
}

37
src/Squidex/app/features/settings/pages/workflows/workflows-page.component.html

@ -1,37 +0,0 @@
<sqx-panel desiredWidth="50rem" isBlank="true" [isLazyLoaded]="false">
<ng-container title>
Workflows
</ng-container>
<ng-container menu>
<button type="button" class="btn btn-text-secondary" (click)="reload()" title="Refresh roles (CTRL + SHIFT + R)">
<i class="icon-reset"></i> Refresh
</button>
<sqx-shortcut keys="ctrl+shift+r" (trigger)="reload()"></sqx-shortcut>
<ng-container>
<button type="button" class="btn btn-primary" (click)="save()" title="Save (CTRL + S)">
Save
</button>
<sqx-shortcut keys="ctrl+s" (trigger)="save()"></sqx-shortcut>
</ng-container>
</ng-container>
<ng-container content>
<sqx-workflow-step *ngFor="let step of workflow.steps; trackBy: trackByStep"
[workflow]="workflow"
[step]="step"
(rename)="renameStep(step, $event)"
(remove)="removeStep(step)"
(transitionAdd)="addTransiton(step, $event)"
(transitionRemove)="removeTransition(step, $event)"
(update)="updateStep(step, $event)">
</sqx-workflow-step>
<button class="btn btn-success" (click)="addStep()">
Add Step
</button>
</ng-container>
</sqx-panel>

2
src/Squidex/app/features/settings/pages/workflows/workflows-page.component.scss

@ -1,2 +0,0 @@
@import '_vars';
@import '_mixins';

65
src/Squidex/app/features/settings/pages/workflows/workflows-page.component.ts

@ -1,65 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, OnInit } from '@angular/core';
import {
WorkflowDto,
WorkflowStep,
WorkflowStepValues,
WorkflowTransition
} from '@app/shared';
@Component({
selector: 'sqx-workflows-page',
styleUrls: ['./workflows-page.component.scss'],
templateUrl: './workflows-page.component.html'
})
export class WorkflowsPageComponent implements OnInit {
public workflow: WorkflowDto;
public ngOnInit() {
this.workflow = new WorkflowDto().setStep('Published', { color: 'green', isLocked: true });
}
public reload() {
return;
}
public save() {
return;
}
public addStep() {
this.workflow = this.workflow.setStep(`Step${this.workflow.steps.length + 1}`, {});
}
public addTransiton(from: WorkflowStep, to: WorkflowStep) {
this.workflow = this.workflow.setTransition(from.name, to.name, {});
}
public removeTransition(from: WorkflowStep, transition: WorkflowTransition) {
this.workflow = this.workflow.removeTransition(from.name, transition.to);
}
public updateStep(step: WorkflowStep, values: WorkflowStepValues) {
this.workflow = this.workflow.setStep(step.name, values);
}
public renameStep(step: WorkflowStep, newName: string) {
this.workflow = this.workflow.renameStep(step.name, newName);
}
public removeStep(step: WorkflowStep) {
this.workflow = this.workflow.removeStep(step.name);
}
public trackByStep(index: number, step: WorkflowStep) {
return step.name;
}
}

6
src/Squidex/app/features/settings/settings-area.component.html

@ -43,12 +43,6 @@
<i class="icon-angle-right"></i>
</a>
</li>
<li class="nav-item" *ngIf="selectedApp.canReadWorkflows">
<a class="nav-link" routerLink="workflows" routerLinkActive="active">
Workflows
<i class="icon-angle-right"></i>
</a>
</li>
<li class="nav-item" *ngIf="selectedApp.canReadPlans">
<a class="nav-link" routerLink="plans" routerLinkActive="active">
Subscription

6
src/Squidex/app/framework/angular/panel-container.directive.ts

@ -55,6 +55,12 @@ export class PanelContainerDirective implements AfterViewInit {
const panels = this.panels;
for (let panel of panels) {
if (!panel.isViewInit) {
return;
}
}
let currentSize = 0;
let panelsWidthSpread = 0;

14
src/Squidex/app/framework/angular/panel.component.ts

@ -25,6 +25,8 @@ export class PanelComponent implements AfterViewInit, OnDestroy, OnInit {
public renderWidth = 0;
public isViewInit = false;
@Input()
public theme = 'light';
@ -82,32 +84,38 @@ export class PanelComponent implements AfterViewInit, OnDestroy, OnInit {
}
public ngAfterViewInit() {
this.isViewInit = true;
this.container.invalidate();
}
public measure(size: string) {
if (this.styleWidth !== size) {
if (this.styleWidth !== size && this.isViewInit) {
this.styleWidth = size;
const element = this.panel ? this.panel.nativeElement : undefined;
const element = this.panel.nativeElement;
if (element) {
this.renderer.setStyle(element, 'width', size);
this.renderer.setStyle(element, 'minWidth', this.minWidth);
this.renderWidth = element.offsetWidth;
}
}
}
public arrange(left: any, layer: any) {
const element = this.panel ? this.panel.nativeElement : undefined;
if (this.isViewInit) {
const element = this.panel.nativeElement;
if (element) {
this.renderer.setStyle(element, 'top', '0px');
this.renderer.setStyle(element, 'left', left);
this.renderer.setStyle(element, 'bottom', '0px');
this.renderer.setStyle(element, 'position', 'absolute');
this.renderer.setStyle(element, 'z-index', layer);
}
}
}
}

1
src/Squidex/app/shared/internal.ts

@ -32,7 +32,6 @@ export * from './services/ui.service';
export * from './services/usages.service';
export * from './services/users-provider.service';
export * from './services/users.service';
export * from './services/workflows.service';
export * from './state/apps.forms';
export * from './state/apps.state';

188
src/Squidex/app/shared/services/workflows.service.spec.ts

@ -1,188 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { WorkflowDto } from '@app/shared/internal';
describe('Workflow', () => {
it('should create empty workflow', () => {
const workflow = new WorkflowDto();
expect(workflow.name).toEqual('Default');
});
it('should add step to workflow', () => {
const workflow =
new WorkflowDto()
.setStep('1', { color: '#00ff00' });
expect(simplify(workflow)).toEqual({
_links: {},
steps: [
{ name: '1', color: '#00ff00' }
],
transitions: [],
name: 'Default'
});
});
it('should override settings if step already exists', () => {
const workflow =
new WorkflowDto()
.setStep('1', { color: '#00ff00' })
.setStep('1', { color: 'red' });
expect(simplify(workflow)).toEqual({
_links: {},
steps: [
{ name: '1', color: 'red' }
],
transitions: [],
name: 'Default'
});
});
it('should not remove step if locked', () => {
const workflow =
new WorkflowDto()
.setStep('1', { color: '#00ff00', isLocked: true })
.setStep('2', { color: '#ff0000' })
.setTransition('1', '2', { expression: '1 === 2' })
.removeStep('1');
expect(simplify(workflow)).toEqual({
_links: {},
steps: [
{ name: '1', color: '#00ff00', isLocked: true },
{ name: '2', color: '#ff0000' }
],
transitions: [
{ from: '1', to: '2', expression: '1 === 2' }
],
name: 'Default'
});
});
it('should remove step', () => {
const workflow =
new WorkflowDto()
.setStep('1', { color: '#00ff00' })
.setStep('2', { color: '#ff0000' })
.setStep('3', { color: '#0000ff' })
.setTransition('1', '2', { expression: '1 === 2' })
.setTransition('1', '3', { expression: '1 === 3' })
.setTransition('2', '3', { expression: '2 === 3' })
.removeStep('1');
expect(simplify(workflow)).toEqual({
_links: {},
steps: [
{ name: '2', color: '#ff0000' },
{ name: '3', color: '#0000ff' }
],
transitions: [
{ from: '2', to: '3', expression: '2 === 3' }
],
name: 'Default'
});
});
it('should rename step', () => {
const workflow =
new WorkflowDto()
.setStep('1', { color: '#00ff00' })
.setStep('2', { color: '#ff0000' })
.setStep('3', { color: '#0000ff' })
.setTransition('1', '2', { expression: '1 === 2' })
.setTransition('2', '1', { expression: '2 === 1' })
.setTransition('2', '3', { expression: '2 === 3' })
.renameStep('1', '4');
expect(simplify(workflow)).toEqual({
_links: {},
steps: [
{ name: '4', color: '#00ff00' },
{ name: '2', color: '#ff0000' },
{ name: '3', color: '#0000ff' }
],
transitions: [
{ from: '4', to: '2', expression: '1 === 2' },
{ from: '2', to: '4', expression: '2 === 1' },
{ from: '2', to: '3', expression: '2 === 3' }
],
name: 'Default'
});
});
it('should add transitions to workflow', () => {
const workflow =
new WorkflowDto()
.setStep('1', { color: '#00ff00' })
.setStep('2', { color: '#ff0000' })
.setTransition('1', '2', { expression: '1 === 2' })
.setTransition('2', '1', { expression: '2 === 1' });
expect(simplify(workflow)).toEqual({
_links: {},
steps: [
{ name: '1', color: '#00ff00' },
{ name: '2', color: '#ff0000' }
],
transitions: [
{ from: '1', to: '2', expression: '1 === 2' },
{ from: '2', to: '1', expression: '2 === 1' }
],
name: 'Default'
});
});
it('should add remove transition from workflow', () => {
const workflow =
new WorkflowDto()
.setStep('1', { color: '#00ff00' })
.setStep('2', { color: '#ff0000' })
.setTransition('1', '2', { expression: '1 === 1' })
.setTransition('2', '1', { expression: '2 === 1' })
.removeTransition('1', '2');
expect(simplify(workflow)).toEqual({
_links: {},
steps: [
{ name: '1', color: '#00ff00' },
{ name: '2', color: '#ff0000' }
],
transitions: [
{ from: '2', to: '1', expression: '2 === 1' }
],
name: 'Default'
});
});
it('should override settings if transition already exists', () => {
const workflow =
new WorkflowDto()
.setStep('1', { color: '#00ff00' })
.setStep('2', { color: '#ff0000' })
.setTransition('1', '2', { expression: '1 === 2' })
.setTransition('1', '2', { expression: '1 !== 2' });
expect(simplify(workflow)).toEqual({
_links: {},
steps: [
{ name: '1', color: '#00ff00' },
{ name: '2', color: '#ff0000' }
],
transitions: [
{ from: '1', to: '2', expression: '1 !== 2' }
],
name: 'Default'
});
});
});
function simplify(value: any) {
return JSON.parse(JSON.stringify(value));
}

121
src/Squidex/app/shared/services/workflows.service.ts

@ -1,121 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import {
compareStringsAsc,
Model,
ResourceLinks
} from '@app/framework';
export class WorkflowDto extends Model<WorkflowDto> {
public readonly _links: ResourceLinks;
constructor(links: ResourceLinks = {},
public readonly name: string = 'Default',
public readonly steps: WorkflowStep[] = [],
public readonly transitions: WorkflowTransition[] = []
) {
super();
this._links = links;
}
public onCloned() {
this.steps.sort((a, b) => compareStringsAsc(a.name, b.name));
}
public getOpenSteps(step: WorkflowStep) {
return this.steps.filter(x => x.name !== step.name && !this.transitions.find(y => y.from === step.name && y.to === x.name));
}
public getTransitions(step: WorkflowStep): WorkflowTransitionView[] {
return this.transitions.filter(x => x.from === step.name).map(x => ({ step: this.getStep(x.to), ...x }));
}
public getStep(name: string): WorkflowStep {
return this.steps.find(x => x.name === name)!;
}
public setStep(name: string, values: Partial<WorkflowStepValues>) {
const steps = [...this.steps.filter(s => s.name !== name), { name, ...values }];
return this.with({ steps });
}
public removeStep(name: string) {
const steps = this.steps.filter(s => s.name !== name || s.isLocked);
const transitions =
steps.length !== this.steps.length ?
this.transitions.filter(t => t.from !== name && t.to !== name) :
this.transitions;
return this.with({ steps, transitions });
}
public renameStep(name: string, newName: string) {
const steps = this.steps.map(step => {
if (step.name === name) {
return { ...step, name: newName };
}
return step;
});
const transitions = this.transitions.map(transition => {
if (transition.from === name || transition.to === name) {
let newTransition = { ...transition };
if (newTransition.from === name) {
newTransition.from = newName;
}
if (newTransition.to === name) {
newTransition.to = newName;
}
return newTransition;
}
return transition;
});
return this.with({ steps, transitions });
}
public removeTransition(from: string, to: string) {
const transitions = this.transitions.filter(t => t.from !== from || t.to !== to);
return this.with({ transitions });
}
public setTransition(from: string, to: string, values: Partial<WorkflowTransitionValues>) {
const stepFrom = this.steps.find(s => s.name === from);
if (!stepFrom) {
return this;
}
const stepTo = this.steps.find(s => s.name === to);
if (!stepTo) {
return this;
}
const transitions = [...this.transitions.filter(t => t.from !== from || t.to !== to), { from, to, ...values }];
return this.with({ transitions });
}
}
export type WorkflowStepValues = { color?: string; isLocked?: boolean; };
export type WorkflowStep = { name: string } & WorkflowStepValues;
export type WorkflowTransitionValues = { expression?: string };
export type WorkflowTransition = { from: string; to: string } & WorkflowTransitionValues;
export type WorkflowTransitionView = { step: WorkflowStep } & WorkflowTransition;

4
src/Squidex/package-lock.json

@ -10295,7 +10295,7 @@
},
"node-fetch": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz",
"resolved": "http://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz",
"integrity": "sha1-q4hOjn5X44qUR1POxwb3iNF2i7U="
},
"node-forge": {
@ -17761,7 +17761,7 @@
},
"whatwg-fetch": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz",
"resolved": "http://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz",
"integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng=="
},
"which": {

Loading…
Cancel
Save