mirror of https://github.com/Squidex/squidex.git
37 changed files with 725 additions and 169 deletions
@ -0,0 +1,59 @@ |
|||
<div class="step"> |
|||
<div class="row no-gutters"> |
|||
<div class="col-auto color pr-2"> |
|||
<sqx-color-picker mode="Circle" |
|||
[ngModel]="step.color" |
|||
(ngModelChange)="changeColor($event)"> |
|||
</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"> |
|||
<div class="col-auto"> |
|||
<i class="icon-arrow-right text-decent"></i> |
|||
</div> |
|||
<div class="col-3 pl-2"> |
|||
<div class="color-circle" [style.background]="'red'"></div> In Progress |
|||
</div> |
|||
<div class="col pl-2"> |
|||
<input class="form-control" placeholder="Expression" /> |
|||
</div> |
|||
<div class="col-auto pl-2"> |
|||
<button type="button" class="btn btn-text-danger" (click)="remove.emit()"> |
|||
<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"> |
|||
<ng-template let-target="$implicit"> |
|||
<span class="autocomplete-user"> |
|||
<div class="color-circle" [style.background]="target.color"></div> {{target.name}} |
|||
</span> |
|||
</ng-template> |
|||
</sqx-dropdown> |
|||
</div> |
|||
<div class="col pl-2"> |
|||
<button class="btn btn-success"> |
|||
Add Transition |
|||
</button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
@ -0,0 +1,28 @@ |
|||
@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; |
|||
} |
|||
|
|||
.transition { |
|||
& { |
|||
padding-left: 1rem; |
|||
margin-top: .25rem; |
|||
margin-bottom: .5rem; |
|||
line-height: 2rem; |
|||
} |
|||
} |
|||
|
|||
.step { |
|||
& { |
|||
margin-bottom: 1rem; |
|||
} |
|||
} |
|||
@ -0,0 +1,62 @@ |
|||
/* |
|||
* 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 transitionRemove = new EventEmitter<WorkflowTransition>(); |
|||
|
|||
@Output() |
|||
public update = new EventEmitter<WorkflowStepValues>(); |
|||
|
|||
@Output() |
|||
public rename = new EventEmitter<string>(); |
|||
|
|||
@Output() |
|||
public remove = new EventEmitter(); |
|||
|
|||
public openSteps: WorkflowStep[]; |
|||
|
|||
public transitions: WorkflowTransitionView[]; |
|||
|
|||
public ngOnChanges(changes: SimpleChanges) { |
|||
if (changes['workflow'] || changes['step']) { |
|||
this.openSteps = this.workflow.getOpenSteps(this.step); |
|||
|
|||
this.transitions = this.workflow.getTransitions(this.step); |
|||
} |
|||
} |
|||
|
|||
public changeName(name: string) { |
|||
this.rename.emit(name); |
|||
} |
|||
|
|||
public changeColor(color: string) { |
|||
this.update.emit({ color }); |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,35 @@ |
|||
<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" |
|||
[workflow]="workflow" |
|||
[step]="step" |
|||
(update)="updateStep(step, $event)" |
|||
(rename)="renameStep(step, $event)" |
|||
(remove)="removeStep(step)"> |
|||
</sqx-workflow-step> |
|||
|
|||
<button class="btn btn-success" (click)="addStep()"> |
|||
Add Step |
|||
</button> |
|||
</ng-container> |
|||
</sqx-panel> |
|||
@ -0,0 +1,2 @@ |
|||
@import '_vars'; |
|||
@import '_mixins'; |
|||
@ -0,0 +1,48 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
|||
*/ |
|||
|
|||
import { Component } from '@angular/core'; |
|||
|
|||
import { |
|||
WorkflowDto, |
|||
WorkflowStep, |
|||
WorkflowStepValues |
|||
} from '@app/shared'; |
|||
|
|||
@Component({ |
|||
selector: 'sqx-workflows-page', |
|||
styleUrls: ['./workflows-page.component.scss'], |
|||
templateUrl: './workflows-page.component.html' |
|||
}) |
|||
export class WorkflowsPageComponent { |
|||
public workflow = new WorkflowDto().setStep('Published', { color: 'green' }); |
|||
|
|||
public reload() { |
|||
return; |
|||
} |
|||
|
|||
public save() { |
|||
return; |
|||
} |
|||
|
|||
public addStep() { |
|||
this.workflow = this.workflow.setStep(`Step${this.workflow.steps.length + 1}`, {}); |
|||
} |
|||
|
|||
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);
|
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,26 @@ |
|||
<span> |
|||
<div class="selection"> |
|||
<input type="text" class="form-control" [disabled]="snapshot.isDisabled" (click)="open()" readonly (keydown)="onKeyDown($event)" #input |
|||
autocomplete="off" |
|||
autocorrect="off" |
|||
autocapitalize="off"> |
|||
|
|||
<div class="control-dropdown-item" *ngIf="snapshot.selectedItem"> |
|||
<ng-container *ngIf="!selectionTemplate">{{snapshot.selectedItem}}</ng-container> |
|||
|
|||
<ng-template *ngIf="selectionTemplate" [sqxTemplateWrapper]="selectionTemplate" [item]="snapshot.selectedItem"></ng-template> |
|||
</div> |
|||
|
|||
<i class="icon-caret-down"></i> |
|||
</div> |
|||
|
|||
<div class="items-container"> |
|||
<div class="control-dropdown" #container *sqxModalView="dropdown" [sqxModalTarget]="input" position="bottomLeft"> |
|||
<div *ngFor="let item of items; let i = index;" class="control-dropdown-item control-dropdown-item-selectable" [class.active]="i === snapshot.selectedIndex" (mousedown)="selectIndexAndClose(i)" [sqxScrollActive]="i === snapshot.selectedIndex" [container]="container"> |
|||
<ng-container *ngIf="!itemTemplate">{{item}}</ng-container> |
|||
|
|||
<ng-template *ngIf="itemTemplate" [sqxTemplateWrapper]="itemTemplate" [item]="item" [index]="i"></ng-template> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</span> |
|||
@ -0,0 +1,38 @@ |
|||
@import '_mixins'; |
|||
@import '_vars'; |
|||
|
|||
$color-input-disabled: #eef1f4; |
|||
|
|||
.form-control { |
|||
& { |
|||
width: 100%; |
|||
} |
|||
|
|||
&[readonly] { |
|||
background: $color-input-background; |
|||
} |
|||
|
|||
&:disabled { |
|||
background: $color-input-disabled; |
|||
} |
|||
} |
|||
|
|||
.selection { |
|||
& { |
|||
position: relative; |
|||
overflow: hidden; |
|||
} |
|||
|
|||
.control-dropdown-item { |
|||
@include absolute(0, 1rem, 0, 0); |
|||
pointer-events: none; |
|||
} |
|||
|
|||
.icon-caret-down { |
|||
@include absolute(30%, .4rem, auto, auto); |
|||
cursor: pointer; |
|||
font-size: .9rem; |
|||
font-weight: normal; |
|||
pointer-events: none; |
|||
} |
|||
} |
|||
@ -0,0 +1,130 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
|||
*/ |
|||
|
|||
import { AfterContentInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChildren, forwardRef, Input, QueryList, TemplateRef } from '@angular/core'; |
|||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; |
|||
|
|||
import { Keys, ModalModel, StatefulControlComponent } from '@app/framework/internal'; |
|||
|
|||
export const SQX_DROPDOWN_CONTROL_VALUE_ACCESSOR: any = { |
|||
provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => DropdownComponent), multi: true |
|||
}; |
|||
|
|||
interface State { |
|||
selectedItem: any; |
|||
selectedIndex: number; |
|||
} |
|||
|
|||
@Component({ |
|||
selector: 'sqx-dropdown', |
|||
styleUrls: ['./dropdown.component.scss'], |
|||
templateUrl: './dropdown.component.html', |
|||
providers: [SQX_DROPDOWN_CONTROL_VALUE_ACCESSOR], |
|||
changeDetection: ChangeDetectionStrategy.OnPush |
|||
}) |
|||
export class DropdownComponent extends StatefulControlComponent<State, any[]> implements AfterContentInit, ControlValueAccessor { |
|||
@Input() |
|||
public items: any[] = []; |
|||
|
|||
@ContentChildren(TemplateRef) |
|||
public templates: QueryList<any>; |
|||
|
|||
public dropdown = new ModalModel(); |
|||
|
|||
public selectionTemplate: TemplateRef<any>; |
|||
|
|||
public itemTemplate: TemplateRef<any>; |
|||
|
|||
constructor(changeDetector: ChangeDetectorRef) { |
|||
super(changeDetector, { |
|||
selectedItem: undefined, |
|||
selectedIndex: -1 |
|||
}); |
|||
} |
|||
|
|||
public ngAfterContentInit() { |
|||
if (this.templates.length === 1) { |
|||
this.itemTemplate = this.selectionTemplate = this.templates.first; |
|||
} else { |
|||
this.templates.forEach(template => { |
|||
if (template.name === 'selection') { |
|||
this.selectionTemplate = template; |
|||
} else { |
|||
this.itemTemplate = template; |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
|
|||
public writeValue(obj: any) { |
|||
this.selectIndex(this.items && obj ? this.items.indexOf(obj) : 0); |
|||
} |
|||
|
|||
public onKeyDown(event: KeyboardEvent) { |
|||
switch (event.keyCode) { |
|||
case Keys.UP: |
|||
this.up(); |
|||
return false; |
|||
case Keys.DOWN: |
|||
this.down(); |
|||
return false; |
|||
case Keys.ESCAPE: |
|||
case Keys.ENTER: |
|||
if (this.dropdown.isOpen) { |
|||
this.close(); |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
public open() { |
|||
this.dropdown.show(); |
|||
|
|||
this.callTouched(); |
|||
} |
|||
|
|||
public selectIndexAndClose(selectedIndex: number) { |
|||
this.selectIndex(selectedIndex); |
|||
|
|||
this.close(); |
|||
} |
|||
|
|||
private close() { |
|||
this.dropdown.hide(); |
|||
} |
|||
|
|||
public selectIndex(selectedIndex: number) { |
|||
if (selectedIndex < 0) { |
|||
selectedIndex = 0; |
|||
} |
|||
|
|||
const items = this.items || []; |
|||
|
|||
if (selectedIndex >= items.length) { |
|||
selectedIndex = items.length - 1; |
|||
} |
|||
|
|||
const value = items[selectedIndex]; |
|||
|
|||
if (value !== this.snapshot.selectedItem) { |
|||
selectedIndex = selectedIndex; |
|||
|
|||
this.next(s => ({ ...s, selectedIndex, selectedItem: value })); |
|||
} |
|||
|
|||
} |
|||
|
|||
private up() { |
|||
this.selectIndex(this.snapshot.selectedIndex - 1); |
|||
} |
|||
|
|||
private down() { |
|||
this.selectIndex(this.snapshot.selectedIndex + 1); |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
<div class="title"> |
|||
<form *ngIf="isRenaming; else noRenaming" class="form-inline" [formGroup]="renameForm" (ngSubmit)="rename()"> |
|||
<div class="form-group mr-1"> |
|||
<sqx-control-errors for="name"></sqx-control-errors> |
|||
|
|||
<input type="text" class="form-control form-underlined" formControlName="name" maxlength="20" sqxFocusOnInit (keydown)="onKeyDown($event.keyCode)" /> |
|||
</div> |
|||
|
|||
<button type="submit" class="btn btn-primary" [disabled]="!renameForm.valid || !renameForm.dirty">Save</button> |
|||
|
|||
<button type="button" class="btn btn-text-secondary btn-cancel" (click)="toggleRename()"> |
|||
<i class="icon-close"></i> |
|||
</button> |
|||
</form> |
|||
|
|||
<ng-template #noRenaming> |
|||
<h3 class="title-name" (dblclick)="toggleRename()"> |
|||
{{name}} |
|||
</h3> |
|||
|
|||
<i class="title-edit icon-pencil" *ngIf="!disabled" (click)="toggleRename()"></i> |
|||
</ng-template> |
|||
</div> |
|||
@ -0,0 +1,33 @@ |
|||
@import '_vars'; |
|||
@import '_mixins'; |
|||
|
|||
.title { |
|||
&-edit { |
|||
color: $color-border-dark; |
|||
display: none; |
|||
font-size: .9rem; |
|||
font-weight: normal; |
|||
padding: .6rem .25rem; |
|||
border: 0; |
|||
background: transparent; |
|||
vertical-align: baseline; |
|||
} |
|||
|
|||
&-name { |
|||
padding: .375rem 0; |
|||
font-family: -apple-system, system-ui, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; |
|||
font-size: 1.2rem; |
|||
font-weight: normal; |
|||
line-height: 1.5rem; |
|||
border-top: 1px solid transparent; |
|||
border-bottom: 1px solid transparent; |
|||
display: inline-block; |
|||
margin: 0; |
|||
} |
|||
|
|||
&:hover { |
|||
.title-edit { |
|||
display: inline-block; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,72 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
|||
*/ |
|||
|
|||
import { Component, EventEmitter, Input, Output } from '@angular/core'; |
|||
import { FormBuilder, Validators } from '@angular/forms'; |
|||
|
|||
const ESCAPE_KEY = 27; |
|||
|
|||
@Component({ |
|||
selector: 'sqx-editable-title', |
|||
styleUrls: ['./editable-title.component.scss'], |
|||
templateUrl: './editable-title.component.html' |
|||
}) |
|||
export class EditableTitleComponent { |
|||
@Input() |
|||
public disabled = false; |
|||
|
|||
@Input() |
|||
public name: string; |
|||
|
|||
@Output() |
|||
public nameChanged = new EventEmitter<string>(); |
|||
|
|||
public isRenaming = false; |
|||
|
|||
public renameForm = this.formBuilder.group({ |
|||
name: ['', |
|||
[ |
|||
Validators.required |
|||
] |
|||
] |
|||
}); |
|||
|
|||
constructor( |
|||
private readonly formBuilder: FormBuilder |
|||
) { |
|||
} |
|||
|
|||
public onKeyDown(keyCode: number) { |
|||
if (keyCode === ESCAPE_KEY) { |
|||
this.toggleRename(); |
|||
} |
|||
} |
|||
|
|||
public toggleRename() { |
|||
if (this.disabled) { |
|||
return; |
|||
} |
|||
|
|||
this.renameForm.setValue({ name: this.name }); |
|||
|
|||
this.isRenaming = !this.isRenaming; |
|||
} |
|||
|
|||
public rename() { |
|||
if (this.disabled) { |
|||
return; |
|||
} |
|||
|
|||
if (this.renameForm.valid) { |
|||
const value = this.renameForm.value; |
|||
|
|||
this.nameChanged.emit(value.name); |
|||
|
|||
this.toggleRename(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
|||
*/ |
|||
|
|||
export module Keys { |
|||
export const COMMA = 188; |
|||
export const DELETE = 8; |
|||
export const ENTER = 13; |
|||
export const ESCAPE = 27; |
|||
export const DOWN = 40; |
|||
export const UP = 38; |
|||
} |
|||
Binary file not shown.
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 94 KiB |
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Loading…
Reference in new issue