Browse Source

Merge pull request #118 from Squidex/feature-onboarding

Feature onboarding
pull/131/head
Sebastian Stehle 9 years ago
committed by GitHub
parent
commit
f2d82f884c
  1. 2
      src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.html
  2. 8
      src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.ts
  3. 8
      src/Squidex/app/features/administration/pages/users/users-page.component.html
  4. 2
      src/Squidex/app/features/apps/pages/apps-page.component.html
  5. 4
      src/Squidex/app/features/assets/pages/assets-page.component.html
  6. 8
      src/Squidex/app/features/assets/pages/assets-page.component.ts
  7. 6
      src/Squidex/app/features/content/pages/content/content-field.component.html
  8. 6
      src/Squidex/app/features/content/pages/content/content-page.component.html
  9. 16
      src/Squidex/app/features/content/pages/contents/contents-page.component.html
  10. 4
      src/Squidex/app/features/content/pages/schemas/schemas-page.component.html
  11. 8
      src/Squidex/app/features/content/shared/assets-editor.component.ts
  12. 2
      src/Squidex/app/features/content/shared/content-item.component.html
  13. 2
      src/Squidex/app/features/dashboard/pages/dashboard-page.component.html
  14. 2
      src/Squidex/app/features/schemas/pages/schema/field.component.html
  15. 34
      src/Squidex/app/features/schemas/pages/schema/schema-page.component.html
  16. 6
      src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.html
  17. 2
      src/Squidex/app/features/settings/pages/clients/client.component.html
  18. 12
      src/Squidex/app/features/settings/pages/clients/clients-page.component.html
  19. 12
      src/Squidex/app/features/settings/pages/contributors/contributors-page.component.html
  20. 8
      src/Squidex/app/features/settings/pages/languages/language.component.ts
  21. 12
      src/Squidex/app/features/settings/pages/languages/languages-page.component.html
  22. 6
      src/Squidex/app/features/webhooks/pages/webhook-events-page.component.scss
  23. 12
      src/Squidex/app/features/webhooks/pages/webhooks-page.component.html
  24. 62
      src/Squidex/app/framework/angular/autocomplete.component.ts
  25. 2
      src/Squidex/app/framework/angular/control-errors.component.html
  26. 2
      src/Squidex/app/framework/angular/dialog-renderer.component.html
  27. 2
      src/Squidex/app/framework/angular/dropdown.component.scss
  28. 45
      src/Squidex/app/framework/angular/image-source.directive.ts
  29. 165
      src/Squidex/app/framework/angular/modal-target.directive.ts
  30. 15
      src/Squidex/app/framework/angular/modal-view.directive.ts
  31. 19
      src/Squidex/app/framework/angular/onboarding-tooltip.component.html
  32. 28
      src/Squidex/app/framework/angular/onboarding-tooltip.component.scss
  33. 95
      src/Squidex/app/framework/angular/onboarding-tooltip.component.ts
  34. 8
      src/Squidex/app/framework/angular/panel-container.directive.ts
  35. 56
      src/Squidex/app/framework/angular/rich-editor.component.ts
  36. 22
      src/Squidex/app/framework/angular/slider.component.ts
  37. 12
      src/Squidex/app/framework/angular/template-wrapper.directive.ts
  38. 17
      src/Squidex/app/framework/angular/user-report.component.ts
  39. 4
      src/Squidex/app/framework/declarations.ts
  40. 5
      src/Squidex/app/framework/module.ts
  41. 4
      src/Squidex/app/framework/services/local-store.service.ts
  42. 0
      src/Squidex/app/framework/services/message-bus.service.spec.ts
  43. 0
      src/Squidex/app/framework/services/message-bus.service.ts
  44. 41
      src/Squidex/app/framework/services/onboarding.service.ts
  45. 4
      src/Squidex/app/shared/components/asset.component.html
  46. 2
      src/Squidex/app/shared/components/language-selector.component.html
  47. 4
      src/Squidex/app/shell/pages/internal/apps-menu.component.html
  48. 2
      src/Squidex/app/shell/pages/internal/profile-menu.component.html
  49. 1
      src/Squidex/app/theme/_bootstrap.scss
  50. 2
      src/Squidex/app/theme/_forms.scss
  51. 6
      src/Squidex/app/theme/_lists.scss
  52. 55
      src/Squidex/app/theme/_mixins.scss

2
src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.html

@ -75,7 +75,7 @@
</div> </div>
</sqx-panel> </sqx-panel>
<div class="modal" *sqxModalView="eventConsumerErrorDialog;onRoot:true" [@fade]> <div class="modal" *sqxModalView="eventConsumerErrorDialog;onRoot:true" @fade>
<div class="modal-backdrop"></div> <div class="modal-backdrop"></div>
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">

8
src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.ts

@ -39,6 +39,10 @@ export class EventConsumersPageComponent extends ComponentBase implements OnDest
super(dialogs); super(dialogs);
} }
public ngOnDestroy() {
this.subscription.unsubscribe();
}
public ngOnInit() { public ngOnInit() {
this.load(false, true); this.load(false, true);
@ -48,10 +52,6 @@ export class EventConsumersPageComponent extends ComponentBase implements OnDest
}); });
} }
public ngOnDestroy() {
this.subscription.unsubscribe();
}
public load(showInfo = false, showError = false) { public load(showInfo = false, showError = false) {
this.eventConsumersService.getEventConsumers() this.eventConsumersService.getEventConsumers()
.subscribe(dtos => { .subscribe(dtos => {

8
src/Squidex/app/features/administration/pages/users/users-page.component.html

@ -9,14 +9,14 @@
</button> </button>
<sqx-shortcut keys="ctrl+shift+r" (trigger)="load(true)"></sqx-shortcut> <sqx-shortcut keys="ctrl+shift+r" (trigger)="load(true)"></sqx-shortcut>
<sqx-shortcut keys="ctrl+shift+f" (trigger)="findInput.focus()"></sqx-shortcut> <sqx-shortcut keys="ctrl+shift+f" (trigger)="inputFind.focus()"></sqx-shortcut>
<sqx-shortcut keys="ctrl+shift+g" (trigger)="newButton.click()"></sqx-shortcut> <sqx-shortcut keys="ctrl+shift+g" (trigger)="buttonNew.click()"></sqx-shortcut>
<form class="form-inline" (ngSubmit)="search()"> <form class="form-inline" (ngSubmit)="search()">
<input class="form-control" #findInput [formControl]="usersFilter" placeholder="Search for user" /> <input class="form-control" #inputFind [formControl]="usersFilter" placeholder="Search for user" />
</form> </form>
<button class="btn btn-success" #newButton routerLink="new" title="New User (CTRL + M)"> <button class="btn btn-success" #buttonNew routerLink="new" title="New User (CTRL + M)">
<i class="icon-plus"></i> New <i class="icon-plus"></i> New
</button> </button>
</div> </div>

2
src/Squidex/app/features/apps/pages/apps-page.component.html

@ -16,7 +16,7 @@
</div> </div>
</div> </div>
<div class="modal" *sqxModalView="addAppDialog;onRoot:true" [@fade]> <div class="modal" *sqxModalView="addAppDialog;onRoot:true" @fade>
<div class="modal-backdrop"></div> <div class="modal-backdrop"></div>
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">

4
src/Squidex/app/features/assets/pages/assets-page.component.html

@ -9,10 +9,10 @@
</button> </button>
<sqx-shortcut keys="ctrl+shift+r" (trigger)="load(true)"></sqx-shortcut> <sqx-shortcut keys="ctrl+shift+r" (trigger)="load(true)"></sqx-shortcut>
<sqx-shortcut keys="ctrl+shift+f" (trigger)="findInput.focus()"></sqx-shortcut> <sqx-shortcut keys="ctrl+shift+f" (trigger)="inputFind.focus()"></sqx-shortcut>
<form class="form-inline" (ngSubmit)="search()"> <form class="form-inline" (ngSubmit)="search()">
<input class="form-control" #findInput [formControl]="assetsFilter" placeholder="Search for assets" /> <input class="form-control" #inputFind [formControl]="assetsFilter" placeholder="Search for assets" />
</form> </form>
</div> </div>

8
src/Squidex/app/features/assets/pages/assets-page.component.ts

@ -46,6 +46,10 @@ export class AssetsPageComponent extends AppComponentBase implements OnDestroy,
super(dialogs, apps, authService); super(dialogs, apps, authService);
} }
public ngOnDestroy() {
this.assetUpdatedSubscription.unsubscribe();
}
public ngOnInit() { public ngOnInit() {
this.assetUpdatedSubscription = this.assetUpdatedSubscription =
this.messageBus.of(AssetUpdated) this.messageBus.of(AssetUpdated)
@ -58,10 +62,6 @@ export class AssetsPageComponent extends AppComponentBase implements OnDestroy,
this.load(); this.load();
} }
public ngOnDestroy() {
this.assetUpdatedSubscription.unsubscribe();
}
public search() { public search() {
this.assetsPager = new Pager(0, 0, 12); this.assetsPager = new Pager(0, 0, 12);
this.assertQuery = this.assetsFilter.value; this.assertQuery = this.assetsFilter.value;

6
src/Squidex/app/features/content/pages/content/content-field.component.html

@ -7,7 +7,11 @@
<div [formGroup]="fieldForm"> <div [formGroup]="fieldForm">
<div *ngIf="field.partitioning === 'language' && languages.length > 1"> <div *ngIf="field.partitioning === 'language' && languages.length > 1">
<sqx-language-selector size="sm" class="languages-buttons" (selectedLanguageChanged)="selectLanguage($event)" [languages]="languages"></sqx-language-selector> <sqx-language-selector size="sm" class="languages-buttons" (selectedLanguageChanged)="selectLanguage($event)" [languages]="languages" #languages></sqx-language-selector>
<sqx-onboarding-tooltip id="languages" [for]="languages" position="topRight" after="120000">
Please remember to check all languages when you see validation errors.
</sqx-onboarding-tooltip>
</div> </div>
<div *ngFor="let partition of fieldPartitions"> <div *ngFor="let partition of fieldPartitions">

6
src/Squidex/app/features/content/pages/content/content-page.component.html

@ -46,12 +46,16 @@
</div> </div>
</div> </div>
<div class="panel-sidebar"> <div class="panel-sidebar">
<a class="panel-link" routerLink="history" routerLinkActive="active" *ngIf="!isNewMode"> <a class="panel-link" routerLink="history" routerLinkActive="active" #linkHistory *ngIf="!isNewMode">
<i class="icon-time"></i> <i class="icon-time"></i>
</a> </a>
<a class="panel-link" routerLink="assets" routerLinkActive="active"> <a class="panel-link" routerLink="assets" routerLinkActive="active">
<i class="icon-media"></i> <i class="icon-media"></i>
</a> </a>
<sqx-onboarding-tooltip id="history" [for]="linkHistory" position="leftTop" after="120000" *ngIf="!isNewMode">
The sidebar navigation contains useful context specific links. Here you can view the history how this schema has changed over time.
</sqx-onboarding-tooltip>
</div> </div>
</div> </div>
</sqx-panel> </sqx-panel>

16
src/Squidex/app/features/content/pages/contents/contents-page.component.html

@ -9,18 +9,26 @@
</button> </button>
<sqx-shortcut keys="ctrl+shift+r" (trigger)="load(true)"></sqx-shortcut> <sqx-shortcut keys="ctrl+shift+r" (trigger)="load(true)"></sqx-shortcut>
<sqx-shortcut keys="ctrl+shift+f" (trigger)="findInput.focus()"></sqx-shortcut> <sqx-shortcut keys="ctrl+shift+f" (trigger)="inputFind.focus()"></sqx-shortcut>
<sqx-shortcut keys="ctrl+shift+g" (trigger)="newButton.click()" *ngIf="!isReadOnly"></sqx-shortcut> <sqx-shortcut keys="ctrl+shift+g" (trigger)="newButton.click()" *ngIf="!isReadOnly"></sqx-shortcut>
<form class="form-inline" (ngSubmit)="search()"> <form class="form-inline" (ngSubmit)="search()">
<input class="form-control form-control-expandable" #findInput [formControl]="contentsFilter" placeholder="Search for content" #searchInput /> <input class="form-control form-control-expandable" #inputFind [formControl]="contentsFilter" placeholder="Search for content" #searchInput />
<a class="expand-search" (click)="searchModal.toggle()"> <a class="expand-search" (click)="searchModal.toggle()" #archive>
<i class="icon-caret-down"></i> <i class="icon-caret-down"></i>
</a> </a>
</form> </form>
<sqx-onboarding-tooltip id="contentArchive" [for]="archive" position="bottomRight" after="60000">
Click this icon to show the advanced search menu and to show the archive!
</sqx-onboarding-tooltip>
<sqx-onboarding-tooltip id="contentFind" [for]="inputFind" position="bottomRight" after="120000">
Search for content using full text search over all fields and languages!
</sqx-onboarding-tooltip>
<div class="dropdown-menu" *sqxModalView="searchModal" closeAlways="true" [sqxModalTarget]="searchInput" position="right"> <div class="dropdown-menu" *sqxModalView="searchModal" closeAlways="true" [sqxModalTarget]="searchInput">
<sqx-search-form <sqx-search-form
[canArchive]="!isReadOnly" [canArchive]="!isReadOnly"
(queryChanged)="contentsFilter.setValue($event, { emitEvent: false })" (queryChanged)="contentsFilter.setValue($event, { emitEvent: false })"

4
src/Squidex/app/features/content/pages/schemas/schemas-page.component.html

@ -11,10 +11,10 @@
</a> </a>
<div class="panel-header-row"> <div class="panel-header-row">
<sqx-shortcut keys="ctrl+shift+f" (trigger)="findInput.focus()"></sqx-shortcut> <sqx-shortcut keys="ctrl+shift+f" (trigger)="inputFind.focus()"></sqx-shortcut>
<div class="search-form"> <div class="search-form">
<input class="form-control form-control-dark" #findInput [formControl]="schemasFilter" placeholder="Search for schemas" /> <input class="form-control form-control-dark" #inputFind [formControl]="schemasFilter" placeholder="Search for schemas" />
<i class="icon-search"></i> <i class="icon-search"></i>
</div> </div>

8
src/Squidex/app/features/content/shared/assets-editor.component.ts

@ -51,6 +51,10 @@ export class AssetsEditorComponent extends AppComponentBase implements ControlVa
super(dialogs, apps, authService); super(dialogs, apps, authService);
} }
public ngOnDestroy() {
this.assetUpdatedSubscription.unsubscribe();
}
public ngOnInit() { public ngOnInit() {
this.assetUpdatedSubscription = this.assetUpdatedSubscription =
this.messageBus.of(AssetUpdated) this.messageBus.of(AssetUpdated)
@ -61,10 +65,6 @@ export class AssetsEditorComponent extends AppComponentBase implements ControlVa
}); });
} }
public ngOnDestroy() {
this.assetUpdatedSubscription.unsubscribe();
}
public writeValue(value: string[]) { public writeValue(value: string[]) {
this.oldAssets = ImmutableArray.empty<AssetDto>(); this.oldAssets = ImmutableArray.empty<AssetDto>();

2
src/Squidex/app/features/content/shared/content-item.component.html

@ -16,7 +16,7 @@
<button type="button" class="btn btn-link btn-decent" (click)="dropdown.toggle(); $event.stopPropagation()" [class.active]="dropdown.isOpen | async" #optionsButton> <button type="button" class="btn btn-link btn-decent" (click)="dropdown.toggle(); $event.stopPropagation()" [class.active]="dropdown.isOpen | async" #optionsButton>
<i class="icon-dots"></i> <i class="icon-dots"></i>
</button> </button>
<div class="dropdown-menu" *sqxModalView="dropdown" closeAlways="true" [sqxModalTarget]="optionsButton" position="right" [@fade]> <div class="dropdown-menu" *sqxModalView="dropdown" closeAlways="true" [sqxModalTarget]="optionsButton" @fade>
<a class="dropdown-item" (click)="publishing.emit(); $event.stopPropagation()" *ngIf="content.status === 'Draft'"> <a class="dropdown-item" (click)="publishing.emit(); $event.stopPropagation()" *ngIf="content.status === 'Draft'">
Publish Publish
</a> </a>

2
src/Squidex/app/features/dashboard/pages/dashboard-page.component.html

@ -1,6 +1,6 @@
<sqx-title message="{app} | Dashboard" parameter1="app" value1="{{appName() | async}}"></sqx-title> <sqx-title message="{app} | Dashboard" parameter1="app" value1="{{appName() | async}}"></sqx-title>
<div class="dashboard" [@fade]> <div class="dashboard" @fade>
<div class="dashboard-inner"> <div class="dashboard-inner">
<div> <div>

2
src/Squidex/app/features/schemas/pages/schema/field.component.html

@ -26,7 +26,7 @@
<button type="button" class="btn btn-link btn-decent" (click)="dropdown.toggle()" [class.active]="dropdown.isOpen | async" #optionsButton> <button type="button" class="btn btn-link btn-decent" (click)="dropdown.toggle()" [class.active]="dropdown.isOpen | async" #optionsButton>
<i class="icon-dots"></i> <i class="icon-dots"></i>
</button> </button>
<div class="dropdown-menu" *sqxModalView="dropdown" closeAlways="true" [sqxModalTarget]="optionsButton" position="right" [@fade]> <div class="dropdown-menu" *sqxModalView="dropdown" closeAlways="true" [sqxModalTarget]="optionsButton" @fade>
<a class="dropdown-item" (click)="enabling.emit()" *ngIf="field.isDisabled" [class.disabled]="field.isLocked"> <a class="dropdown-item" (click)="enabling.emit()" *ngIf="field.isDisabled" [class.disabled]="field.isLocked">
Enable Enable
</a> </a>

34
src/Squidex/app/features/schemas/pages/schema/schema-page.component.html

@ -8,7 +8,7 @@
JSON Preview JSON Preview
</button> </button>
<div class="btn-group" data-toggle="buttons"> <div class="btn-group" data-toggle="buttons" #buttonPublish>
<button type="button" class="btn btn-publishing btn-toggle" [class.btn-success]="schema.isPublished" [disabled]="schema.isPublished" (click)="publish()"> <button type="button" class="btn btn-publishing btn-toggle" [class.btn-success]="schema.isPublished" [disabled]="schema.isPublished" (click)="publish()">
Published Published
</button> </button>
@ -18,10 +18,10 @@
</div> </div>
<div class="dropdown dropdown-options"> <div class="dropdown dropdown-options">
<button type="button" class="btn btn-link btn-decent" (click)="editOptionsDropdown.toggle()" [class.active]="editOptionsDropdown.isOpen | async" #optionsButton> <button type="button" class="btn btn-link btn-decent" (click)="editOptionsDropdown.toggle()" [class.active]="editOptionsDropdown.isOpen | async" #buttonOptions>
<i class="icon-dots"></i> <i class="icon-dots"></i>
</button> </button>
<div class="dropdown-menu" *sqxModalView="editOptionsDropdown" closeAlways="true" [sqxModalTarget]="optionsButton" position="right" [@fade]> <div class="dropdown-menu" *sqxModalView="editOptionsDropdown" closeAlways="true" [sqxModalTarget]="buttonOptions" @fade>
<a class="dropdown-item" (click)="configureScriptsDialog.show()"> <a class="dropdown-item" (click)="configureScriptsDialog.show()">
Scripts Scripts
</a> </a>
@ -30,6 +30,14 @@
</a> </a>
</div> </div>
</div> </div>
<sqx-onboarding-tooltip id="history" [for]="buttonOptions" position="bottomRight" after="60000">
Open the context menu to delete the schema or to create some scripts for content changes.
</sqx-onboarding-tooltip>
<sqx-onboarding-tooltip id="history" [for]="buttonPublish" position="bottomRight" after="240000">
Note, that you have to publish the schema before you can add content to it.
</sqx-onboarding-tooltip>
</div> </div>
<h3 class="panel-title"> <h3 class="panel-title">
@ -76,7 +84,7 @@
<div class="row no-gutters mt-3"> <div class="row no-gutters mt-3">
<div class="col"> <div class="col">
<div class="form-check"> <div class="form-check">
<label class="form-check-label pull-left"> <label class="form-check-label">
<input class="form-check-input" type="checkbox" formControlName="isLocalizable"> Localizable <input class="form-check-input" type="checkbox" formControlName="isLocalizable"> Localizable
</label> </label>
</div> </div>
@ -96,17 +104,25 @@
</div> </div>
</div> </div>
<div class="panel-sidebar"> <div class="panel-sidebar">
<a class="panel-link" routerLink="history" routerLinkActive="active"> <a class="panel-link" routerLink="history" routerLinkActive="active" #linkHistory>
<i class="icon-time"></i> <i class="icon-time"></i>
</a> </a>
<a class="panel-link" routerLink="help" routerLinkActive="active"> <a class="panel-link" routerLink="help" routerLinkActive="active" #linkHelp>
<i class="icon-help"></i> <i class="icon-help"></i>
</a> </a>
<sqx-onboarding-tooltip id="history" [for]="linkHistory" position="leftTop" after="120000">
The sidebar navigation contains useful context specific links. Here you can view the history how this schema has changed over time.
</sqx-onboarding-tooltip>
<sqx-onboarding-tooltip id="help" [for]="linkHelp" position="leftTop" after="180000">
Click the help icon to show a context specific help page. Go to <a href="https://docs.squidex.io" target="_blank">https://docs.squidex.io</a> for the full documentation.
</sqx-onboarding-tooltip>
</div> </div>
</div> </div>
</sqx-panel> </sqx-panel>
<div class="modal" *sqxModalView="editSchemaDialog;onRoot:true" [@fade]> <div class="modal" *sqxModalView="editSchemaDialog;onRoot:true" @fade>
<div class="modal-backdrop"></div> <div class="modal-backdrop"></div>
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
@ -125,7 +141,7 @@
</div> </div>
</div> </div>
<div class="modal" *sqxModalView="exportSchemaDialog;onRoot:true" [@fade]> <div class="modal" *sqxModalView="exportSchemaDialog;onRoot:true" @fade>
<div class="modal-backdrop"></div> <div class="modal-backdrop"></div>
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
@ -144,7 +160,7 @@
</div> </div>
</div> </div>
<div class="modal" *sqxModalView="configureScriptsDialog;onRoot:true" [@fade]> <div class="modal" *sqxModalView="configureScriptsDialog;onRoot:true" @fade>
<div class="modal-backdrop"></div> <div class="modal-backdrop"></div>
<div class="modal-dialog modal-lg"> <div class="modal-dialog modal-lg">
<div class="modal-content"> <div class="modal-content">

6
src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.html

@ -12,14 +12,14 @@
<div class="panel-header-row"> <div class="panel-header-row">
<sqx-shortcut keys="ctrl+shift+g" (trigger)="addSchemaDialog.show()"></sqx-shortcut> <sqx-shortcut keys="ctrl+shift+g" (trigger)="addSchemaDialog.show()"></sqx-shortcut>
<sqx-shortcut keys="ctrl+shift+f" (trigger)="findInput.focus()"></sqx-shortcut> <sqx-shortcut keys="ctrl+shift+f" (trigger)="inputFind.focus()"></sqx-shortcut>
<button class="btn btn-success subheader-button" (click)="addSchemaDialog.show()" title="New Schema (CTRL + SHIFT + G)"> <button class="btn btn-success subheader-button" (click)="addSchemaDialog.show()" title="New Schema (CTRL + SHIFT + G)">
<i class="icon-plus"></i> <i class="icon-plus"></i>
</button> </button>
<div class="search-form"> <div class="search-form">
<input class="form-control form-control-dark" #findInput [formControl]="schemasFilter" placeholder="Search for schemas..." /> <input class="form-control form-control-dark" #inputFind [formControl]="schemasFilter" placeholder="Search for schemas..." />
<i class="icon-search"></i> <i class="icon-search"></i>
</div> </div>
@ -53,7 +53,7 @@
</div> </div>
</sqx-panel> </sqx-panel>
<div class="modal" *sqxModalView="addSchemaDialog;onRoot:true" [@fade]> <div class="modal" *sqxModalView="addSchemaDialog;onRoot:true" @fade>
<div class="modal-backdrop"></div> <div class="modal-backdrop"></div>
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">

2
src/Squidex/app/features/settings/pages/clients/client.component.html

@ -82,7 +82,7 @@
</table> </table>
</div> </div>
<div class="modal" *sqxModalView="tokenDialog;onRoot:true" [@fade]> <div class="modal" *sqxModalView="tokenDialog;onRoot:true" @fade>
<div class="modal-backdrop"></div> <div class="modal-backdrop"></div>
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">

12
src/Squidex/app/features/settings/pages/clients/clients-page.component.html

@ -43,12 +43,20 @@
</div> </div>
</div> </div>
<div class="panel-sidebar"> <div class="panel-sidebar">
<a class="panel-link" routerLink="history" routerLinkActive="active"> <a class="panel-link" routerLink="history" routerLinkActive="active" #linkHistory>
<i class="icon-time"></i> <i class="icon-time"></i>
</a> </a>
<a class="panel-link" routerLink="help" routerLinkActive="active"> <a class="panel-link" routerLink="help" routerLinkActive="active" #linkHelp>
<i class="icon-help"></i> <i class="icon-help"></i>
</a> </a>
<sqx-onboarding-tooltip id="history" [for]="linkHistory" position="leftTop" after="120000">
The sidebar navigation contains useful context specific links. Here you can view the history how this schema has changed over time.
</sqx-onboarding-tooltip>
<sqx-onboarding-tooltip id="help" [for]="linkHelp" position="leftTop" after="180000">
Click the help icon to show a context specific help page. Go to <a href="https://docs.squidex.io" target="_blank">https://docs.squidex.io</a> for the full documentation.
</sqx-onboarding-tooltip>
</div> </div>
</div> </div>
</sqx-panel> </sqx-panel>

12
src/Squidex/app/features/settings/pages/contributors/contributors-page.component.html

@ -75,12 +75,20 @@
</div> </div>
</div> </div>
<div class="panel-sidebar"> <div class="panel-sidebar">
<a class="panel-link" routerLink="history" routerLinkActive="active"> <a class="panel-link" routerLink="history" routerLinkActive="active" #linkHistory>
<i class="icon-time"></i> <i class="icon-time"></i>
</a> </a>
<a class="panel-link" routerLink="help" routerLinkActive="active"> <a class="panel-link" routerLink="help" routerLinkActive="active" #linkHelp>
<i class="icon-help"></i> <i class="icon-help"></i>
</a> </a>
<sqx-onboarding-tooltip id="history" [for]="linkHistory" position="leftTop" after="120000">
The sidebar navigation contains useful context specific links. Here you can view the history how this schema has changed over time.
</sqx-onboarding-tooltip>
<sqx-onboarding-tooltip id="help" [for]="linkHelp" position="leftTop" after="180000">
Click the help icon to show a context specific help page. Go to <a href="https://docs.squidex.io" target="_blank">https://docs.squidex.io</a> for the full documentation.
</sqx-onboarding-tooltip>
</div> </div>
</div> </div>
</sqx-panel> </sqx-panel>

8
src/Squidex/app/features/settings/pages/languages/language.component.ts

@ -54,6 +54,10 @@ export class LanguageComponent implements OnInit, OnChanges, OnDestroy {
) { ) {
} }
public ngOnDestroy() {
this.isMasterSubscription.unsubscribe();
}
public ngOnInit() { public ngOnInit() {
this.isMasterSubscription = this.isMasterSubscription =
this.editForm.controls['isMaster'].valueChanges this.editForm.controls['isMaster'].valueChanges
@ -65,10 +69,6 @@ export class LanguageComponent implements OnInit, OnChanges, OnDestroy {
this.resetEditForm(); this.resetEditForm();
} }
public ngOnDestroy() {
this.isMasterSubscription.unsubscribe();
}
public ngOnChanges() { public ngOnChanges() {
this.resetEditForm(); this.resetEditForm();
} }

12
src/Squidex/app/features/settings/pages/languages/languages-page.component.html

@ -35,12 +35,20 @@
</div> </div>
</div> </div>
<div class="panel-sidebar"> <div class="panel-sidebar">
<a class="panel-link" routerLink="history" routerLinkActive="active"> <a class="panel-link" routerLink="history" routerLinkActive="active" #linkHistory>
<i class="icon-time"></i> <i class="icon-time"></i>
</a> </a>
<a class="panel-link" routerLink="help" routerLinkActive="active"> <a class="panel-link" routerLink="help" routerLinkActive="active" #linkHelp>
<i class="icon-help"></i> <i class="icon-help"></i>
</a> </a>
<sqx-onboarding-tooltip id="history" [for]="linkHistory" position="leftTop" after="120000">
The sidebar navigation contains useful context specific links. Here you can view the history how this schema has changed over time.
</sqx-onboarding-tooltip>
<sqx-onboarding-tooltip id="help" [for]="linkHelp" position="leftTop" after="180000">
Click the help icon to show a context specific help page. Go to <a href="https://docs.squidex.io" target="_blank">https://docs.squidex.io</a> for the full documentation.
</sqx-onboarding-tooltip>
</div> </div>
</div> </div>
</sqx-panel> </sqx-panel>

6
src/Squidex/app/features/webhooks/pages/webhook-events-page.component.scss

@ -30,10 +30,8 @@ h3 {
} }
&::before { &::before {
@include caret-top; @include caret-top($color-border);
@include absolute(-.5rem, 1.9rem, auto, auto); @include absolute(-1.1rem, 1.9rem, auto, auto);
border-color: transparent transparent $color-border;
border-width: .6rem;
} }
h3 { h3 {

12
src/Squidex/app/features/webhooks/pages/webhooks-page.component.html

@ -51,12 +51,20 @@
</div> </div>
<div class="panel-sidebar"> <div class="panel-sidebar">
<a class="panel-link" routerLink="events" routerLinkActive="active"> <a class="panel-link" routerLink="events" routerLinkActive="active" #linkHistory>
<i class="icon-time"></i> <i class="icon-time"></i>
</a> </a>
<a class="panel-link" routerLink="help" routerLinkActive="active"> <a class="panel-link" routerLink="help" routerLinkActive="active" #linkHelp>
<i class="icon-help"></i> <i class="icon-help"></i>
</a> </a>
<sqx-onboarding-tooltip id="history" [for]="linkHistory" position="leftTop" after="120000">
The sidebar navigation contains useful context specific links. Here you can view the history how this schema has changed over time.
</sqx-onboarding-tooltip>
<sqx-onboarding-tooltip id="help" [for]="linkHelp" position="leftTop" after="180000">
Click the help icon to show a context specific help page. Go to <a href="https://docs.squidex.io" target="_blank">https://docs.squidex.io</a> for the full documentation.
</sqx-onboarding-tooltip>
</div> </div>
</div> </div>
</sqx-panel> </sqx-panel>

62
src/Squidex/app/framework/angular/autocomplete.component.ts

@ -54,37 +54,6 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O
public queryInput = new FormControl(); public queryInput = new FormControl();
public writeValue(value: any) {
if (!value) {
this.resetForm();
} else {
const item = this.items.find(i => i === value);
if (item) {
this.queryInput.setValue(value.title || '');
}
}
this.reset();
}
public setDisabledState(isDisabled: boolean): void {
if (isDisabled) {
this.reset();
this.queryInput.disable();
} else {
this.queryInput.enable();
}
}
public registerOnChange(fn: any) {
this.callChange = fn;
}
public registerOnTouched(fn: any) {
this.callTouched = fn;
}
public ngOnDestroy() { public ngOnDestroy() {
this.subscription.unsubscribe(); this.subscription.unsubscribe();
} }
@ -132,6 +101,37 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O
return true; return true;
} }
public writeValue(value: any) {
if (!value) {
this.resetForm();
} else {
const item = this.items.find(i => i === value);
if (item) {
this.queryInput.setValue(value.title || '');
}
}
this.reset();
}
public setDisabledState(isDisabled: boolean): void {
if (isDisabled) {
this.reset();
this.queryInput.disable();
} else {
this.queryInput.enable();
}
}
public registerOnChange(fn: any) {
this.callChange = fn;
}
public registerOnTouched(fn: any) {
this.callTouched = fn;
}
public blur() { public blur() {
this.reset(); this.reset();
this.callTouched(); this.callTouched();

2
src/Squidex/app/framework/angular/control-errors.component.html

@ -1,4 +1,4 @@
<div class="errors-container" *ngIf="errorMessages" [@fade]> <div class="errors-container" *ngIf="errorMessages" @fade>
<div class="errors"> <div class="errors">
<span *ngFor="let message of errorMessages"> <span *ngFor="let message of errorMessages">
{{message}} {{message}}

2
src/Squidex/app/framework/angular/dialog-renderer.component.html

@ -21,7 +21,7 @@
</div> </div>
<div class="notification-container notification-container-{{position}}"> <div class="notification-container notification-container-{{position}}">
<div class="alert alert-dismissible alert-{{notification.messageType}}" *ngFor="let notification of notifications" (click)="close(notification)" [@fade]> <div class="alert alert-dismissible alert-{{notification.messageType}}" *ngFor="let notification of notifications" (click)="close(notification)" @fade>
<button type="button" class="close" data-dismiss="alert" (close)="close(notification)">&times;</button> <button type="button" class="close" data-dismiss="alert" (close)="close(notification)">&times;</button>
{{notification.message}} {{notification.message}}

2
src/Squidex/app/framework/angular/dropdown.component.scss

@ -30,7 +30,9 @@ $color-input-disabled: #eef1f4;
.icon-caret-down { .icon-caret-down {
@include absolute(30%, .4rem, auto, auto); @include absolute(30%, .4rem, auto, auto);
cursor: pointer;
font-size: .9rem; font-size: .9rem;
font-weight: normal; font-weight: normal;
pointer-events: none;
} }
} }

45
src/Squidex/app/framework/angular/image-source.directive.ts

@ -5,14 +5,17 @@
* Copyright (c) Sebastian Stehle. All rights reserved * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
import { AfterViewInit, Directive, ElementRef, HostListener, Input, OnChanges, OnInit, Renderer } from '@angular/core'; import { AfterViewInit, Directive, ElementRef, HostListener, Input, OnChanges, OnDestroy, OnInit, Renderer } from '@angular/core';
import { MathHelper } from './../utils/math-helper'; import { MathHelper } from './../utils/math-helper';
@Directive({ @Directive({
selector: '[sqxImageSource]' selector: '[sqxImageSource]'
}) })
export class ImageSourceDirective implements OnChanges, OnInit, AfterViewInit { export class ImageSourceDirective implements OnChanges, OnDestroy, OnInit, AfterViewInit {
private parentResizeListener: Function;
private loadingTimer: any;
private size: any; private size: any;
private loadRetries = 0; private loadRetries = 0;
private loadQuery: string | null = null; private loadQuery: string | null = null;
@ -32,15 +35,10 @@ export class ImageSourceDirective implements OnChanges, OnInit, AfterViewInit {
) { ) {
} }
public ngOnChanges() { public ngOnDestroy() {
this.loadQuery = null; clearTimeout(this.loadingTimer);
this.loadRetries = 0;
this.setImageSource();
}
public ngAfterViewInit() { this.parentResizeListener();
this.resize(this.parent);
} }
public ngOnInit() { public ngOnInit() {
@ -48,9 +46,21 @@ export class ImageSourceDirective implements OnChanges, OnInit, AfterViewInit {
this.parent = this.element.nativeElement.parentElement; this.parent = this.element.nativeElement.parentElement;
} }
this.renderer.listen(this.parent, 'resize', () => { this.parentResizeListener =
this.resize(this.parent); this.renderer.listen(this.parent, 'resize', () => {
}); this.resize(this.parent);
});
}
public ngAfterViewInit() {
this.resize(this.parent);
}
public ngOnChanges() {
this.loadQuery = null;
this.loadRetries = 0;
this.setImageSource();
} }
@HostListener('load') @HostListener('load')
@ -98,11 +108,12 @@ export class ImageSourceDirective implements OnChanges, OnInit, AfterViewInit {
this.loadRetries++; this.loadRetries++;
if (this.loadRetries <= 10) { if (this.loadRetries <= 10) {
setTimeout(() => { this.loadingTimer =
this.loadQuery = MathHelper.guid(); setTimeout(() => {
this.loadQuery = MathHelper.guid();
this.setImageSource(); this.setImageSource();
}, this.loadRetries * 1000); }, this.loadRetries * 1000);
} }
} }
} }

165
src/Squidex/app/framework/angular/modal-target.directive.ts

@ -6,7 +6,16 @@
*/ */
import { AfterViewInit, Directive, ElementRef, Input, OnDestroy, OnInit, Renderer } from '@angular/core'; import { AfterViewInit, Directive, ElementRef, Input, OnDestroy, OnInit, Renderer } from '@angular/core';
import { Observable, Subscription } from 'rxjs';
const POSITION_TOPLEFT = 'topLeft';
const POSITION_TOPRIGHT = 'topRight';
const POSITION_BOTTOMLEFT = 'bottomLeft';
const POSITION_BOTTOMRIGHT = 'bottomRight';
const POSITION_LEFTTOP = 'leftTop';
const POSITION_LEFTBOTTOM = 'leftBottom';
const POSITION_RIGHTTOP = 'rightTop';
const POSITION_RIGHTBOTTOM = 'rightBottom';
const POSITION_FULL = 'full';
@Directive({ @Directive({
selector: '[sqxModalTarget]' selector: '[sqxModalTarget]'
@ -14,7 +23,7 @@ import { Observable, Subscription } from 'rxjs';
export class ModalTargetDirective implements AfterViewInit, OnDestroy, OnInit { export class ModalTargetDirective implements AfterViewInit, OnDestroy, OnInit {
private elementResizeListener: Function; private elementResizeListener: Function;
private targetResizeListener: Function; private targetResizeListener: Function;
private timer: Subscription; private renderTimer: any;
private targetElement: any; private targetElement: any;
@Input('sqxModalTarget') @Input('sqxModalTarget')
@ -24,7 +33,10 @@ export class ModalTargetDirective implements AfterViewInit, OnDestroy, OnInit {
public offset = 2; public offset = 2;
@Input() @Input()
public position = 'left'; public position = POSITION_BOTTOMRIGHT;
@Input()
public autoPosition = true;
constructor( constructor(
private readonly renderer: Renderer, private readonly renderer: Renderer,
@ -32,6 +44,18 @@ export class ModalTargetDirective implements AfterViewInit, OnDestroy, OnInit {
) { ) {
} }
public ngOnDestroy() {
if (this.targetResizeListener) {
this.targetResizeListener();
}
if (this.elementResizeListener) {
this.elementResizeListener();
}
clearInterval(this.renderTimer);
}
public ngOnInit() { public ngOnInit() {
if (this.target) { if (this.target) {
this.targetElement = this.target; this.targetElement = this.target;
@ -46,29 +70,15 @@ export class ModalTargetDirective implements AfterViewInit, OnDestroy, OnInit {
this.updatePosition(); this.updatePosition();
}); });
this.timer = this.renderTimer =
Observable.timer(100, 100).subscribe(() => { setInterval(() => {
this.updatePosition(); this.updatePosition();
}); }, 100);
}
}
public ngOnDestroy() {
if (this.targetResizeListener) {
this.targetResizeListener();
}
if (this.elementResizeListener) {
this.elementResizeListener();
}
if (this.timer) {
this.timer.unsubscribe();
} }
} }
public ngAfterViewInit() { public ngAfterViewInit() {
const modalRef = this.element.nativeElement; const modalRef = this.element.nativeElement;
this.renderer.setElementStyle(modalRef, 'position', 'fixed'); this.renderer.setElementStyle(modalRef, 'position', 'fixed');
this.renderer.setElementStyle(modalRef, 'z-index', '1000000'); this.renderer.setElementStyle(modalRef, 'z-index', '1000000');
@ -78,28 +88,117 @@ export class ModalTargetDirective implements AfterViewInit, OnDestroy, OnInit {
private updatePosition() { private updatePosition() {
const viewportHeight = document.documentElement.clientHeight; const viewportHeight = document.documentElement.clientHeight;
const viewportWidth = document.documentElement.clientWidth;
const modalRef = this.element.nativeElement; const modalRef = this.element.nativeElement;
const modalRect = this.element.nativeElement.getBoundingClientRect(); const modalRect = this.element.nativeElement.getBoundingClientRect();
const targetRect: ClientRect = this.targetElement.getBoundingClientRect(); const targetRect: ClientRect = this.targetElement.getBoundingClientRect();
const left = this.position === 'left' ? const fix = this.autoPosition;
targetRect.left :
targetRect.right - modalRect.width; let t = 0
let l = 0;
switch (this.position) {
case POSITION_LEFTTOP:
case POSITION_RIGHTTOP:
{
t = targetRect.top;
break;
}
case POSITION_LEFTBOTTOM:
case POSITION_RIGHTBOTTOM:
{
t = targetRect.bottom - modalRect.height;
break;
}
case POSITION_BOTTOMLEFT:
case POSITION_BOTTOMRIGHT:
{
t = targetRect.bottom + this.offset;
if (fix && t + modalRect.height > viewportHeight) {
const candidate = targetRect.top - modalRect.height - this.offset;
if (candidate > 0) {
t = candidate;
}
}
break;
}
case POSITION_TOPLEFT:
case POSITION_TOPRIGHT:
{
t = targetRect.top - modalRect.height - this.offset;
if (fix && t < 0) {
const candidate = targetRect.bottom + this.offset;
if (candidate + modalRect.height > viewportHeight) {
t = candidate;
}
}
break;
}
}
switch (this.position) {
case POSITION_TOPLEFT:
case POSITION_BOTTOMLEFT:
{
l = targetRect.left;
break;
}
case POSITION_TOPRIGHT:
case POSITION_BOTTOMRIGHT:
{
l = targetRect.right - modalRect.width
break;
}
case POSITION_RIGHTTOP:
case POSITION_RIGHTBOTTOM:
{
l = targetRect.right + this.offset;
if (fix && l + modalRect.width > viewportWidth) {
const candidate = targetRect.right - modalRect.width - this.offset;
if (candidate > 0) {
l = candidate;
}
}
break;
}
case POSITION_LEFTTOP:
case POSITION_LEFTBOTTOM:
{
l = targetRect.left - modalRect.width - this.offset;
if (this.autoPosition && l < 0) {
const candidate = targetRect.right + this.offset;
if (candidate + modalRect.width > viewportWidth) {
l = candidate;
}
}
break;
}
}
let top = targetRect.bottom + this.offset; if (this.position === POSITION_FULL) {
t = targetRect.top - this.offset;
l = targetRect.left - this.offset;
if (top + modalRect.height > viewportHeight) { const w = targetRect.width + 2 * this.offset;
const potentialTop = targetRect.top - modalRect.height - this.offset; const h = targetRect.height + 2 * this.offset;
if (potentialTop > 0) { this.renderer.setElementStyle(modalRef, 'width', `${w}px`);
top = potentialTop; this.renderer.setElementStyle(modalRef, 'height', `${h}px`);
}
} }
this.renderer.setElementStyle(modalRef, 'top', top + 'px'); this.renderer.setElementStyle(modalRef, 'top', `${t}px`);
this.renderer.setElementStyle(modalRef, 'left', left + 'px'); this.renderer.setElementStyle(modalRef, 'left', `${l}px`);
this.renderer.setElementStyle(modalRef, 'right', 'auto'); this.renderer.setElementStyle(modalRef, 'right', 'auto');
this.renderer.setElementStyle(modalRef, 'bottom', 'auto'); this.renderer.setElementStyle(modalRef, 'bottom', 'auto');
this.renderer.setElementStyle(modalRef, 'margin', '0'); this.renderer.setElementStyle(modalRef, 'margin', '0');

15
src/Squidex/app/framework/angular/modal-view.directive.ts

@ -17,7 +17,7 @@ import { RootViewService } from './../services/root-view.service';
}) })
export class ModalViewDirective implements OnChanges, OnDestroy { export class ModalViewDirective implements OnChanges, OnDestroy {
private subscription: Subscription | null = null; private subscription: Subscription | null = null;
private clickHandler: Function | null = null; private documentClickListener: Function | null = null;
private renderedView: EmbeddedViewRef<any> | null = null; private renderedView: EmbeddedViewRef<any> | null = null;
@Input('sqxModalView') @Input('sqxModalView')
@ -36,6 +36,8 @@ export class ModalViewDirective implements OnChanges, OnDestroy {
public ngOnDestroy() { public ngOnDestroy() {
this.stopListening(); this.stopListening();
this.modalView.hide();
} }
public ngOnChanges(changes: SimpleChanges) { public ngOnChanges(changes: SimpleChanges) {
@ -49,7 +51,8 @@ export class ModalViewDirective implements OnChanges, OnDestroy {
} }
if (this.modalView) { if (this.modalView) {
this.subscription = this.modalView.isOpen.subscribe(isOpen => { this.subscription =
this.modalView.isOpen.subscribe(isOpen => {
if (isOpen === (this.renderedView !== null)) { if (isOpen === (this.renderedView !== null)) {
return; return;
} }
@ -81,7 +84,7 @@ export class ModalViewDirective implements OnChanges, OnDestroy {
} }
private startListening() { private startListening() {
this.clickHandler = this.documentClickListener =
this.renderer.listenGlobal('document', 'click', (event: MouseEvent) => { this.renderer.listenGlobal('document', 'click', (event: MouseEvent) => {
if (!event.target || this.renderedView === null) { if (!event.target || this.renderedView === null) {
return; return;
@ -109,9 +112,9 @@ export class ModalViewDirective implements OnChanges, OnDestroy {
} }
private stopListening() { private stopListening() {
if (this.clickHandler) { if (this.documentClickListener) {
this.clickHandler(); this.documentClickListener();
this.clickHandler = null; this.documentClickListener = null;
} }
} }
} }

19
src/Squidex/app/framework/angular/onboarding-tooltip.component.html

@ -0,0 +1,19 @@
<div *sqxModalView="tooltipModal;onRoot:true" closeAlways="true" @fade>
<div class="onboarding-rect" [sqxModalTarget]="for" [offset]="4" [autoPosition]="false" position="full"></div>
<div class="onboarding-help" [sqxModalTarget]="for" [offset]="4" [autoPosition]="false" [position]="position">
<div class="onboarding-text">
<ng-content></ng-content>
</div>
<div class="onboarding-buttons clearfix">
<button (click)="hideAll()" class="btn btn-link btn-primary btn-sm float-left">
Stop Tour
</button>
<button (click)="hideThis()" class="btn btn-link btn-success btn-sm float-right">
Got It
</button>
</div>
</div>
</div>

28
src/Squidex/app/framework/angular/onboarding-tooltip.component.scss

@ -0,0 +1,28 @@
@import '_mixins';
@import '_vars';
$color: #1a2129;
.onboarding {
&-help {
@include border-radius;
background: $color;
border: 0;
max-width: 20rem;
padding: .75rem;
}
&-text {
font-size: .9rem;
font-weight: normal;
color: $color-dark-foreground;
}
&-buttons {
margin: .8rem -.5rem 0;
}
&-rect {
@include box-shadow-raw(0 0 0 9999px rgba(0, 0, 0, .5));
}
}

95
src/Squidex/app/framework/angular/onboarding-tooltip.component.ts

@ -0,0 +1,95 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Component, Input, OnDestroy, OnInit, Renderer } from '@angular/core';
import { ModalView } from './../utils/modal-view';
import { OnboardingService } from './../services/onboarding.service';
import { fadeAnimation } from './animations';
@Component({
selector: 'sqx-onboarding-tooltip',
styleUrls: ['./onboarding-tooltip.component.scss'],
templateUrl: './onboarding-tooltip.component.html',
animations: [
fadeAnimation
]
})
export class OnboardingTooltipComponent implements OnDestroy, OnInit {
private showTimer: any;
private closeTimer: any;
private forMouseDownListener: Function;
public tooltipModal = new ModalView();
@Input()
public for: any;
@Input()
public id: string;
@Input()
public after = 1000;
@Input()
public position = 'left';
constructor(
private readonly onboardingService: OnboardingService,
private readonly renderer: Renderer
) {
}
public ngOnDestroy() {
clearTimeout(this.showTimer);
clearTimeout(this.closeTimer);
this.tooltipModal.hide();
if (this.forMouseDownListener) {
this.forMouseDownListener();
this.forMouseDownListener = null;
}
}
public ngOnInit() {
if (this.for && this.id) {
this.showTimer = setTimeout(() => {
if (this.onboardingService.shouldShow(this.id)) {
this.tooltipModal.show();
this.closeTimer = setTimeout(() => {
this.hideThis();
}, 10000);
this.onboardingService.disable(this.id);
}
}, this.after);
this.forMouseDownListener =
this.renderer.listen(this.for, 'mousedown', () => {
this.onboardingService.disable(this.id);
this.hideThis();
});
}
}
public hideThis() {
this.onboardingService.disable(this.id);
this.ngOnDestroy();
}
public hideAll() {
this.onboardingService.disableAll();
this.ngOnDestroy();
}
}

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

@ -28,14 +28,14 @@ export class PanelContainerDirective implements AfterViewInit, OnDestroy {
this.invalidate(); this.invalidate();
} }
public ngAfterViewInit() {
this.invalidate({ force: true, resize: true });
}
public ngOnDestroy() { public ngOnDestroy() {
this.isInit = true; this.isInit = true;
} }
public ngAfterViewInit() {
this.invalidate({ force: true, resize: true });
}
public push(panel: PanelComponent) { public push(panel: PanelComponent) {
this.panels.push(panel); this.panels.push(panel);

56
src/Squidex/app/framework/angular/rich-editor.component.ts

@ -28,6 +28,7 @@ export class RichEditorComponent implements ControlValueAccessor, AfterViewInit,
private callChange = (v: any) => { /* NOOP */ }; private callChange = (v: any) => { /* NOOP */ };
private callTouched = () => { /* NOOP */ }; private callTouched = () => { /* NOOP */ };
private tinyEditor: any; private tinyEditor: any;
private tinyInitTimer: any;
private value: string; private value: string;
private isDisabled = false; private isDisabled = false;
@ -39,28 +40,10 @@ export class RichEditorComponent implements ControlValueAccessor, AfterViewInit,
) { ) {
} }
public writeValue(value: string) { public ngOnDestroy() {
this.value = Types.isString(value) ? value : ''; clearTimeout(this.tinyInitTimer);
if (this.tinyEditor) {
this.tinyEditor.setContent(this.value);
}
}
public setDisabledState(isDisabled: boolean): void {
this.isDisabled = isDisabled;
if (this.tinyEditor) {
this.tinyEditor.setMode(isDisabled ? 'readonly' : 'design');
}
}
public registerOnChange(fn: any) {
this.callChange = fn;
}
public registerOnTouched(fn: any) { tinymce.remove(this.editor);
this.callTouched = fn;
} }
public ngAfterViewInit() { public ngAfterViewInit() {
@ -86,16 +69,37 @@ export class RichEditorComponent implements ControlValueAccessor, AfterViewInit,
self.callTouched(); self.callTouched();
}); });
setTimeout(() => { this.tinyInitTimer =
self.tinyEditor.setContent(this.value || ''); setTimeout(() => {
}, 500); self.tinyEditor.setContent(this.value || '');
}, 500);
}, },
removed_menuitems: 'newdocument', plugins: 'code', target: this.editor.nativeElement removed_menuitems: 'newdocument', plugins: 'code', target: this.editor.nativeElement
}); });
}); });
} }
public ngOnDestroy() { public writeValue(value: string) {
tinymce.remove(this.editor); this.value = Types.isString(value) ? value : '';
if (this.tinyEditor) {
this.tinyEditor.setContent(this.value);
}
}
public setDisabledState(isDisabled: boolean): void {
this.isDisabled = isDisabled;
if (this.tinyEditor) {
this.tinyEditor.setMode(isDisabled ? 'readonly' : 'design');
}
}
public registerOnChange(fn: any) {
this.callChange = fn;
}
public registerOnTouched(fn: any) {
this.callTouched = fn;
} }
} }

22
src/Squidex/app/framework/angular/slider.component.ts

@ -23,8 +23,8 @@ export const SQX_SLIDER_CONTROL_VALUE_ACCESSOR: any = {
export class SliderComponent implements ControlValueAccessor { export class SliderComponent implements ControlValueAccessor {
private callChange = (v: any) => { /* NOOP */ }; private callChange = (v: any) => { /* NOOP */ };
private callTouched = () => { /* NOOP */ }; private callTouched = () => { /* NOOP */ };
private mouseMoveSubscription: Function | null = null; private windowMouseMoveListener: Function | null = null;
private mouseUpSubscription: Function | null = null; private windowMouseUpListener: Function | null = null;
private centerStartOffset = 0; private centerStartOffset = 0;
private startValue: number; private startValue: number;
private lastValue: number; private lastValue: number;
@ -69,7 +69,7 @@ export class SliderComponent implements ControlValueAccessor {
} }
public onBarMouseClick(event: MouseEvent): boolean { public onBarMouseClick(event: MouseEvent): boolean {
if (this.mouseMoveSubscription) { if (this.windowMouseMoveListener) {
return true; return true;
} }
@ -89,12 +89,12 @@ export class SliderComponent implements ControlValueAccessor {
this.startValue = this.value; this.startValue = this.value;
this.mouseMoveSubscription = this.windowMouseMoveListener =
this.renderer.listenGlobal('window', 'mousemove', (e: MouseEvent) => { this.renderer.listenGlobal('window', 'mousemove', (e: MouseEvent) => {
this.onMouseMove(e); this.onMouseMove(e);
}); });
this.mouseUpSubscription = this.windowMouseUpListener =
this.renderer.listenGlobal('window', 'mouseup', () => { this.renderer.listenGlobal('window', 'mouseup', () => {
this.onMouseUp(); this.onMouseUp();
}); });
@ -175,14 +175,14 @@ export class SliderComponent implements ControlValueAccessor {
} }
private releaseMouseHandlers() { private releaseMouseHandlers() {
if (this.mouseMoveSubscription) { if (this.windowMouseMoveListener) {
this.mouseMoveSubscription(); this.windowMouseMoveListener();
this.mouseMoveSubscription = null; this.windowMouseMoveListener = null;
} }
if (this.mouseUpSubscription) { if (this.windowMouseUpListener) {
this.mouseUpSubscription(); this.windowMouseUpListener();
this.mouseUpSubscription = null; this.windowMouseUpListener = null;
} }
this.isDragging = false; this.isDragging = false;

12
src/Squidex/app/framework/angular/template-wrapper.directive.ts

@ -27,6 +27,12 @@ export class TemplateWrapperDirective implements OnDestroy, OnInit, OnChanges {
) { ) {
} }
public ngOnDestroy() {
if (this.view) {
this.view.destroy();
}
}
public ngOnInit() { public ngOnInit() {
this.view = this.viewContainer.createEmbeddedView(this.templateRef, { this.view = this.viewContainer.createEmbeddedView(this.templateRef, {
'\$implicit': this.item, '\$implicit': this.item,
@ -43,10 +49,4 @@ export class TemplateWrapperDirective implements OnDestroy, OnInit, OnChanges {
} }
} }
} }
public ngOnDestroy() {
if (this.view) {
this.view.destroy();
}
}
} }

17
src/Squidex/app/framework/angular/user-report.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Sebastian Stehle. All rights reserved * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
import { Component, OnInit } from '@angular/core'; import { Component, OnDestroy, OnInit } from '@angular/core';
import { ResourceLoaderService } from './../services/resource-loader.service'; import { ResourceLoaderService } from './../services/resource-loader.service';
import { UserReportConfig } from './../configurations'; import { UserReportConfig } from './../configurations';
@ -14,7 +14,9 @@ import { UserReportConfig } from './../configurations';
selector: 'sqx-user-report', selector: 'sqx-user-report',
template: '' template: ''
}) })
export class UserReportComponent implements OnInit { export class UserReportComponent implements OnDestroy, OnInit {
private loadingTimer: any;
constructor(config: UserReportConfig, constructor(config: UserReportConfig,
private readonly resourceLoader: ResourceLoaderService private readonly resourceLoader: ResourceLoaderService
) { ) {
@ -22,9 +24,14 @@ export class UserReportComponent implements OnInit {
window['_urq'].push(['initSite', config.siteId]); window['_urq'].push(['initSite', config.siteId]);
} }
public ngOnDestroy() {
clearTimeout(this.loadingTimer);
}
public ngOnInit() { public ngOnInit() {
setTimeout(() => { this.loadingTimer =
this.resourceLoader.loadScript('https://cdn.userreport.com/userreport.js'); setTimeout(() => {
}, 4000); this.resourceLoader.loadScript('https://cdn.userreport.com/userreport.js');
}, 4000);
} }
} }

4
src/Squidex/app/framework/declarations.ts

@ -30,6 +30,7 @@ export * from './angular/modal-view.directive';
export * from './angular/money.pipe'; export * from './angular/money.pipe';
export * from './angular/name.pipe'; export * from './angular/name.pipe';
export * from './angular/numbers.pipes'; export * from './angular/numbers.pipes';
export * from './angular/onboarding-tooltip.component';
export * from './angular/panel.component'; export * from './angular/panel.component';
export * from './angular/panel-container.directive'; export * from './angular/panel-container.directive';
export * from './angular/parent-link.directive'; export * from './angular/parent-link.directive';
@ -56,7 +57,8 @@ export * from './services/clipboard.service';
export * from './services/dialog.service'; export * from './services/dialog.service';
export * from './services/local-store.service'; export * from './services/local-store.service';
export * from './services/local-cache.service'; export * from './services/local-cache.service';
export * from './services/message-bus'; export * from './services/message-bus.service';
export * from './services/onboarding.service';
export * from './services/resource-loader.service'; export * from './services/resource-loader.service';
export * from './services/root-view.service'; export * from './services/root-view.service';
export * from './services/shortcut.service'; export * from './services/shortcut.service';

5
src/Squidex/app/framework/module.ts

@ -46,6 +46,8 @@ import {
ModalViewDirective, ModalViewDirective,
MoneyPipe, MoneyPipe,
MonthPipe, MonthPipe,
OnboardingService,
OnboardingTooltipComponent,
PanelContainerDirective, PanelContainerDirective,
PanelComponent, PanelComponent,
ParentLinkDirective, ParentLinkDirective,
@ -107,6 +109,7 @@ import {
ModalViewDirective, ModalViewDirective,
MoneyPipe, MoneyPipe,
MonthPipe, MonthPipe,
OnboardingTooltipComponent,
PanelContainerDirective, PanelContainerDirective,
PanelComponent, PanelComponent,
ParentLinkDirective, ParentLinkDirective,
@ -155,6 +158,7 @@ import {
ModalViewDirective, ModalViewDirective,
MoneyPipe, MoneyPipe,
MonthPipe, MonthPipe,
OnboardingTooltipComponent,
PanelContainerDirective, PanelContainerDirective,
PanelComponent, PanelComponent,
ParentLinkDirective, ParentLinkDirective,
@ -193,6 +197,7 @@ export class SqxFrameworkModule {
LocalCacheService, LocalCacheService,
LocalStoreService, LocalStoreService,
MessageBus, MessageBus,
OnboardingService,
ResourceLoaderService, ResourceLoaderService,
RootViewService, RootViewService,
ShortcutService, ShortcutService,

4
src/Squidex/app/framework/services/local-store.service.ts

@ -20,7 +20,7 @@ export class LocalStoreService {
this.store = store; this.store = store;
} }
public get(key: string): any { public get(key: string): string | null {
try { try {
return this.store.getItem(key); return this.store.getItem(key);
} catch (e) { } catch (e) {
@ -28,7 +28,7 @@ export class LocalStoreService {
} }
} }
public set(key: string, value: any) { public set(key: string, value: string) {
try { try {
this.store.setItem(key, value); this.store.setItem(key, value);
} catch (e) { } catch (e) {

0
src/Squidex/app/framework/services/message-bus.spec.ts → src/Squidex/app/framework/services/message-bus.service.spec.ts

0
src/Squidex/app/framework/services/message-bus.ts → src/Squidex/app/framework/services/message-bus.service.ts

41
src/Squidex/app/framework/services/onboarding.service.ts

@ -0,0 +1,41 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Injectable } from '@angular/core';
import { LocalStoreService } from './local-store.service';
export const OnboardingServiceFactory = (localStore: LocalStoreService) => {
return new OnboardingService(localStore);
};
@Injectable()
export class OnboardingService {
constructor(
private readonly localStore: LocalStoreService
) {
}
public disableAll() {
this.disable('all');
}
public disable(key: string) {
this.localStore.set(`squidex.onboarding.disable.${key}`, '1');
}
public shouldShow(key: string) {
return this.shouldShowKey(key) && this.shouldShowKey('all');
}
private shouldShowKey(key: string) {
return this.localStore.get(`squidex.onboarding.disable.${key}`) !== '1';
}
}

4
src/Squidex/app/shared/components/asset.component.html

@ -1,6 +1,6 @@
<div class="card" (sqxFileDrop)="updateFile($event)" dnd-draggable [dragEnabled]="!!asset" [dragData]="asset"> <div class="card" (sqxFileDrop)="updateFile($event)" dnd-draggable [dragEnabled]="!!asset" [dragData]="asset">
<div class="card-block"> <div class="card-block">
<div class="file-preview" *ngIf="asset && progress == 0" [@fade]> <div class="file-preview" *ngIf="asset && progress == 0" @fade>
<span class="file-type" *ngIf="asset.fileType"> <span class="file-type" *ngIf="asset.fileType">
{{asset.fileType}} {{asset.fileType}}
</span> </span>
@ -61,7 +61,7 @@
</div> </div>
</div> </div>
<div class="modal" *sqxModalView="renameDialog;onRoot:true" [@fade]> <div class="modal" *sqxModalView="renameDialog;onRoot:true" @fade>
<div class="modal-backdrop"></div> <div class="modal-backdrop"></div>
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">

2
src/Squidex/app/shared/components/language-selector.component.html

@ -8,7 +8,7 @@
<button type="button" class="btn btn-secondary dropdown-toggle" [attr.title]="selectedLanguage.englishName" (click)="dropdown.toggle(); $event.stopPropagation()" #button> <button type="button" class="btn btn-secondary dropdown-toggle" [attr.title]="selectedLanguage.englishName" (click)="dropdown.toggle(); $event.stopPropagation()" #button>
{{selectedLanguage.iso2Code}} {{selectedLanguage.iso2Code}}
</button> </button>
<div class="dropdown-menu" *sqxModalView="dropdown" closeAlways="true" [sqxModalTarget]="button" position="right" [@fade]> <div class="dropdown-menu" *sqxModalView="dropdown" closeAlways="true" [sqxModalTarget]="button" @fade>
<div class="dropdown-item" *ngFor="let language of languages" [class.active]="language == selectedLanguage" (click)="selectLanguage(language)"> <div class="dropdown-item" *ngFor="let language of languages" [class.active]="language == selectedLanguage" (click)="selectLanguage(language)">
<strong class="iso-code">{{language.iso2Code}}</strong> &nbsp; &nbsp; ({{language.englishName}}) <strong class="iso-code">{{language.iso2Code}}</strong> &nbsp; &nbsp; ({{language.englishName}})
</div> </div>

4
src/Squidex/app/shell/pages/internal/apps-menu.component.html

@ -2,7 +2,7 @@
<li class="nav-item dropdown"> <li class="nav-item dropdown">
<span class="nav-link dropdown-toggle" id="app-name" (click)="modalMenu.toggle()">{{appName}}</span> <span class="nav-link dropdown-toggle" id="app-name" (click)="modalMenu.toggle()">{{appName}}</span>
<div class="dropdown-menu" *sqxModalView="modalMenu" closeAlways="true" [@fade]> <div class="dropdown-menu" *sqxModalView="modalMenu" closeAlways="true" @fade>
<a class="dropdown-item all-apps" routerLink="/app"> <a class="dropdown-item all-apps" routerLink="/app">
<span class="all-apps-text">All Apps</span> <span class="all-apps-text">All Apps</span>
<span class="all-apps-pill badge badge-pill badge-primary">{{apps.length}}</span> <span class="all-apps-pill badge badge-pill badge-primary">{{apps.length}}</span>
@ -23,7 +23,7 @@
</li> </li>
</ul> </ul>
<div class="modal" *sqxModalView="modalDialog;onRoot:true" [@fade]> <div class="modal" *sqxModalView="modalDialog;onRoot:true" @fade>
<div class="modal-backdrop"></div> <div class="modal-backdrop"></div>
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">

2
src/Squidex/app/shell/pages/internal/profile-menu.component.html

@ -8,7 +8,7 @@
</span> </span>
</span> </span>
<div class="dropdown-menu" *sqxModalView="modalMenu" closeAlways="true" [@fade]> <div class="dropdown-menu" *sqxModalView="modalMenu" closeAlways="true" @fade>
<a class="dropdown-item" routerLink="/app/administration" *ngIf="isAdmin"> <a class="dropdown-item" routerLink="/app/administration" *ngIf="isAdmin">
Administration Administration
</a> </a>

1
src/Squidex/app/theme/_bootstrap.scss

@ -317,7 +317,6 @@ a {
// Special button groups // Special button groups
&-group { &-group {
.btn-toggle { .btn-toggle {
& { & {
border: 2px solid $color-border; border: 2px solid $color-border;

2
src/Squidex/app/theme/_forms.scss

@ -92,7 +92,7 @@
@include absolute(2px, auto, auto, 0); @include absolute(2px, auto, auto, 0);
@include border-radius(.25em); @include border-radius(.25em);
@include box-shadow; @include box-shadow;
max-height: 10rem; max-height: 15rem;
border: 1px solid $color-input-border; border: 1px solid $color-input-border;
background: $color-dark-foreground; background: $color-dark-foreground;
padding: .3rem 0; padding: .3rem 0;

6
src/Squidex/app/theme/_lists.scss

@ -122,10 +122,8 @@
// Caret that is placed next to the expand button. // Caret that is placed next to the expand button.
&::before { &::before {
@include caret-top; @include caret-top($color-border);
@include absolute(-.5rem, 5.2rem, auto, auto); @include absolute(-1.1rem, 5.2rem, auto, auto);
border-color: transparent transparent $color-border;
border-width: .6rem;
} }
&-tab { &-tab {

55
src/Squidex/app/theme/_mixins.scss

@ -130,29 +130,40 @@
box-sizing: border-box; box-sizing: border-box;
} }
@mixin caret-top() { @mixin caret-top($color, $size: .6rem) {
display: inline-block; @include caret;
width: 0; border: $size solid transparent;
height: 0; border-bottom-color: $color;
content: ''; }
border-bottom: .3em solid;
border-right: .3em solid transparent; @mixin caret-left($color, $size: .6rem) {
border-left: .3em solid transparent; @include caret;
border: $size solid transparent;
border-right-color: $color;
}
@mixin caret-right($color, $size: .6rem) {
@include caret;
border: $size solid transparent;
border-left-color: $color;
}
@mixin caret-bottom($color, $size: .6rem) {
@include caret;
border: $size solid transparent;
border-top-color: $color;
} }
@mixin caret-bottom() { @mixin caret() {
display: inline-block; display: inline-block;
width: 0; width: 0;
height: 0; height: 0;
content: ''; content: '';
border-top: .3em solid;
border-right: .3em solid transparent;
border-left: .3em solid transparent;
} }
@mixin circle($size) { @mixin circle($size) {
@include border-radius($size * .5); @include border-radius($size * .5);
width: $size; width: $size;
height: $size; height: $size;
} }
@ -223,16 +234,10 @@
box-shadow: inset $x-axis $y-axis $blur rgba(0, 0, 0, $alpha); box-shadow: inset $x-axis $y-axis $blur rgba(0, 0, 0, $alpha);
} }
@mixin box-shadow-raw($shadow) { @mixin box-shadow-raw($shadows...) {
-webkit-box-shadow: $shadow; -webkit-box-shadow: $shadows;
-moz-box-shadow: $shadow; -moz-box-shadow: $shadows;
box-shadow: $shadow; box-shadow: $shadows;
}
@mixin box-shadow-raw($shadow1, $shadow2) {
-webkit-box-shadow: $shadow1, $shadow2;
-moz-box-shadow: $shadow1, $shadow2;
box-shadow: $shadow1, $shadow2;
} }
@mixin border-radius-clip() { @mixin border-radius-clip() {

Loading…
Cancel
Save