Browse Source

Onboarding component

pull/118/head
Sebastian Stehle 9 years ago
parent
commit
3184227907
  1. 12
      src/Squidex/app/features/content/pages/contents/contents-page.component.html
  2. 2
      src/Squidex/app/features/content/shared/content-item.component.html
  3. 2
      src/Squidex/app/features/schemas/pages/schema/field.component.html
  4. 2
      src/Squidex/app/features/schemas/pages/schema/schema-page.component.html
  5. 6
      src/Squidex/app/features/webhooks/pages/webhook-events-page.component.scss
  6. 2
      src/Squidex/app/framework/angular/dropdown.component.scss
  7. 17
      src/Squidex/app/framework/angular/image-source.directive.ts
  8. 54
      src/Squidex/app/framework/angular/modal-target.directive.ts
  9. 10
      src/Squidex/app/framework/angular/modal-view.directive.ts
  10. 16
      src/Squidex/app/framework/angular/onboarding-tooltip.component.html
  11. 46
      src/Squidex/app/framework/angular/onboarding-tooltip.component.scss
  12. 98
      src/Squidex/app/framework/angular/onboarding-tooltip.component.ts
  13. 22
      src/Squidex/app/framework/angular/slider.component.ts
  14. 4
      src/Squidex/app/framework/declarations.ts
  15. 5
      src/Squidex/app/framework/module.ts
  16. 4
      src/Squidex/app/framework/services/local-store.service.ts
  17. 0
      src/Squidex/app/framework/services/message-bus.service.spec.ts
  18. 0
      src/Squidex/app/framework/services/message-bus.service.ts
  19. 41
      src/Squidex/app/framework/services/onboarding.service.ts
  20. 2
      src/Squidex/app/shared/components/language-selector.component.html
  21. 1
      src/Squidex/app/theme/_bootstrap.scss
  22. 2
      src/Squidex/app/theme/_forms.scss
  23. 6
      src/Squidex/app/theme/_lists.scss
  24. 41
      src/Squidex/app/theme/_mixins.scss

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

@ -15,12 +15,20 @@
<form class="form-inline" (ngSubmit)="search()">
<input class="form-control form-control-expandable" #findInput [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>
</a>
</form>
<sqx-onboarding-tooltip id="contentArchive" [for]="archive" position="topRight" after="6000"
text="Click this icon to show the advanced search menu and to show the archive!">
</sqx-onboarding-tooltip>
<sqx-onboarding-tooltip id="contentFind" [for]="findInput" position="topRight" after="120000"
text="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" position="topRight">
<sqx-search-form
[canArchive]="!isReadOnly"
(queryChanged)="contentsFilter.setValue($event, { emitEvent: false })"

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>
<i class="icon-dots"></i>
</button>
<div class="dropdown-menu" *sqxModalView="dropdown" closeAlways="true" [sqxModalTarget]="optionsButton" position="right" [@fade]>
<div class="dropdown-menu" *sqxModalView="dropdown" closeAlways="true" [sqxModalTarget]="optionsButton" position="topRight" [@fade]>
<a class="dropdown-item" (click)="publishing.emit(); $event.stopPropagation()" *ngIf="content.status === 'Draft'">
Publish
</a>

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>
<i class="icon-dots"></i>
</button>
<div class="dropdown-menu" *sqxModalView="dropdown" closeAlways="true" [sqxModalTarget]="optionsButton" position="right" [@fade]>
<div class="dropdown-menu" *sqxModalView="dropdown" closeAlways="true" [sqxModalTarget]="optionsButton" position="topRight" [@fade]>
<a class="dropdown-item" (click)="enabling.emit()" *ngIf="field.isDisabled" [class.disabled]="field.isLocked">
Enable
</a>

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

@ -21,7 +21,7 @@
<button type="button" class="btn btn-link btn-decent" (click)="editOptionsDropdown.toggle()" [class.active]="editOptionsDropdown.isOpen | async" #optionsButton>
<i class="icon-dots"></i>
</button>
<div class="dropdown-menu" *sqxModalView="editOptionsDropdown" closeAlways="true" [sqxModalTarget]="optionsButton" position="right" [@fade]>
<div class="dropdown-menu" *sqxModalView="editOptionsDropdown" closeAlways="true" [sqxModalTarget]="optionsButton" position="topRight" [@fade]>
<a class="dropdown-item" (click)="configureScriptsDialog.show()">
Scripts
</a>

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

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

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

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

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

@ -5,14 +5,16 @@
* 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';
@Directive({
selector: '[sqxImageSource]'
})
export class ImageSourceDirective implements OnChanges, OnInit, AfterViewInit {
export class ImageSourceDirective implements OnChanges, OnDestroy, OnInit, AfterViewInit {
private parentResizeListener: Function;
private size: any;
private loadRetries = 0;
private loadQuery: string | null = null;
@ -43,14 +45,19 @@ export class ImageSourceDirective implements OnChanges, OnInit, AfterViewInit {
this.resize(this.parent);
}
public ngOnDestroy() {
this.parentResizeListener();
}
public ngOnInit() {
if (this.parent === null) {
this.parent = this.element.nativeElement.parentElement;
}
this.renderer.listen(this.parent, 'resize', () => {
this.resize(this.parent);
});
this.parentResizeListener =
this.renderer.listen(this.parent, 'resize', () => {
this.resize(this.parent);
});
}
@HostListener('load')

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

@ -24,7 +24,10 @@ export class ModalTargetDirective implements AfterViewInit, OnDestroy, OnInit {
public offset = 2;
@Input()
public position = 'left';
public position = 'topLeft';
@Input()
public auto = true;
constructor(
private readonly renderer: Renderer,
@ -78,23 +81,56 @@ export class ModalTargetDirective implements AfterViewInit, OnDestroy, OnInit {
private updatePosition() {
const viewportHeight = document.documentElement.clientHeight;
const viewportWidth = document.documentElement.clientWidth;
const modalRef = this.element.nativeElement;
const modalRect = this.element.nativeElement.getBoundingClientRect();
const targetRect: ClientRect = this.targetElement.getBoundingClientRect();
const left = this.position === 'left' ?
targetRect.left :
targetRect.right - modalRect.width;
let top = 0, left = 0;
if (this.position === 'topLeft' || this.position === 'topRight') {
top = targetRect.bottom + this.offset;
if (this.auto && top + modalRect.height > viewportHeight) {
const t = targetRect.top - modalRect.height - this.offset;
if (t > 0) {
top = t;
}
}
} else {
top = targetRect.top - modalRect.height - this.offset;
if (this.auto && top < 0) {
const t = targetRect.bottom + this.offset;
let top = targetRect.bottom + this.offset;
if (t + modalRect.height > viewportHeight) {
top = t;
}
}
}
if (this.position === 'topLeft' || this.position === 'bottomLeft') {
left = targetRect.left + this.offset;
if (this.auto && left + modalRect.width > viewportWidth) {
const l = targetRect.right - modalRect.width - this.offset;
if (l > 0) {
left = l;
}
}
} else {
left = targetRect.right - modalRect.width - this.offset;
if (top + modalRect.height > viewportHeight) {
const potentialTop = targetRect.top - modalRect.height - this.offset;
if (this.auto && left < 0) {
const l = targetRect.left + this.offset;
if (potentialTop > 0) {
top = potentialTop;
if (l + modalRect.width > viewportWidth) {
left = l;
}
}
}

10
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 {
private subscription: Subscription | null = null;
private clickHandler: Function | null = null;
private documentClickListener: Function | null = null;
private renderedView: EmbeddedViewRef<any> | null = null;
@Input('sqxModalView')
@ -81,7 +81,7 @@ export class ModalViewDirective implements OnChanges, OnDestroy {
}
private startListening() {
this.clickHandler =
this.documentClickListener =
this.renderer.listenGlobal('document', 'click', (event: MouseEvent) => {
if (!event.target || this.renderedView === null) {
return;
@ -109,9 +109,9 @@ export class ModalViewDirective implements OnChanges, OnDestroy {
}
private stopListening() {
if (this.clickHandler) {
this.clickHandler();
this.clickHandler = null;
if (this.documentClickListener) {
this.documentClickListener();
this.documentClickListener = null;
}
}
}

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

@ -0,0 +1,16 @@
<div *sqxModalView="tooltipModal;onRoot:true" closeAlways="true" [sqxModalTarget]="for" [position]="position" [offset]="4" [auto]="false">
<div class="onboarding-tooltip" [ngClass]="position">
<div class="arrow1"></div>
<div class="arrow2"></div>
<div class="content">{{text}}</div>
<a (click)="hideThis()" class="hide-this btn-link btn-default btn-sm">
<i class="icon-close"></i>
</a>
<button (click)="hideAll()" class="hide-all btn-link btn-primary btn-sm">
Hide all Tooltips
</button>
</div>
</div>

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

@ -0,0 +1,46 @@
@import '_mixins';
@import '_vars';
$color: $color-theme-blue;
$size-arrow: 10px;
// sass-lint:disable class-name-format
.onboarding-tooltip {
& {
@include box-shadow(0, 2px, 20px, .3);
max-width: 20rem;
background: $color-dark-foreground;
border: 1px solid $color;
position: relative;
}
.content {
padding: 1.6rem 1.6rem 2.5rem;
}
.hide-this {
@include absolute(.1rem, .1rem, auto, auto);
}
.hide-all {
@include absolute(auto, .1rem, .1rem, auto);
}
&.topRight {
& {
margin-right: -$size-arrow - 1;
}
.arrow1 {
@include caret-top($color, $size-arrow);
@include absolute(-$size-arrow * 2, $size-arrow * .5, auto, auto);
}
.arrow2 {
@include caret-top($color-dark-foreground, $size-arrow);
@include absolute(-$size-arrow * 2 + 1, $size-arrow * .5, auto, auto);
}
}
}

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

@ -0,0 +1,98 @@
/*
* 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';
@Component({
selector: 'sqx-onboarding-tooltip',
styleUrls: ['./onboarding-tooltip.component.scss'],
templateUrl: './onboarding-tooltip.component.html'
})
export class OnboardingTooltipComponent implements OnDestroy, OnInit {
private showTimer: any;
private closeTimer: any;
private forClickListener: Function;
public tooltipModal = new ModalView();
@Input()
public for: any;
@Input()
public id: string;
@Input()
public position = 'left';
@Input()
public after = 1000;
@Input()
public text: string;
constructor(
private readonly onboardingService: OnboardingService,
private readonly renderer: Renderer
) {
}
public ngOnDestroy() {
if (this.showTimer) {
clearTimeout(this.showTimer);
this.showTimer = null;
}
if (this.closeTimer) {
clearTimeout(this.closeTimer);
this.closeTimer = null;
}
if (this.forClickListener) {
this.forClickListener();
this.forClickListener = 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.after);
this.forClickListener =
this.renderer.listen(this.for, 'mousedown', () => {
this.onboardingService.disable(this.id);
this.hideThis();
});
}
}
public hideThis() {
this.onboardingService.disable(this.id);
this.tooltipModal.hide();
this.ngOnDestroy();
}
public hideAll() {
this.onboardingService.disableAll();
this.tooltipModal.hide();
this.ngOnDestroy();
}
}

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

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/name.pipe';
export * from './angular/numbers.pipes';
export * from './angular/onboarding-tooltip.component';
export * from './angular/panel.component';
export * from './angular/panel-container.directive';
export * from './angular/parent-link.directive';
@ -56,7 +57,8 @@ export * from './services/clipboard.service';
export * from './services/dialog.service';
export * from './services/local-store.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/root-view.service';
export * from './services/shortcut.service';

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

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

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

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

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>
{{selectedLanguage.iso2Code}}
</button>
<div class="dropdown-menu" *sqxModalView="dropdown" closeAlways="true" [sqxModalTarget]="button" position="right" [@fade]>
<div class="dropdown-menu" *sqxModalView="dropdown" closeAlways="true" [sqxModalTarget]="button" position="topRight" [@fade]>
<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}})
</div>

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

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

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

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

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

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

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

@ -130,29 +130,40 @@
box-sizing: border-box;
}
@mixin caret-top() {
display: inline-block;
width: 0;
height: 0;
content: '';
border-bottom: .3em solid;
border-right: .3em solid transparent;
border-left: .3em solid transparent;
@mixin caret-top($color, $size: .6rem) {
@include caret;
border: $size solid transparent;
border-bottom-color: $color;
}
@mixin caret-left($color, $size: .6rem) {
@include caret;
border: $size solid transparent;
border-right-color: $color;
}
@mixin caret-bottom() {
@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() {
display: inline-block;
width: 0;
height: 0;
width: 0;
height: 0;
content: '';
border-top: .3em solid;
border-right: .3em solid transparent;
border-left: .3em solid transparent;
}
@mixin circle($size) {
@include border-radius($size * .5);
width: $size;
width: $size;
height: $size;
}

Loading…
Cancel
Save