Browse Source

Onboarding Dialog.

pull/131/head
Sebastian Stehle 8 years ago
parent
commit
587dba2a05
  1. 4
      README.md
  2. 3
      src/Squidex/app/features/apps/declarations.ts
  3. 6
      src/Squidex/app/features/apps/module.ts
  4. 4
      src/Squidex/app/features/apps/pages/apps-page.component.html
  5. 29
      src/Squidex/app/features/apps/pages/apps-page.component.ts
  6. 154
      src/Squidex/app/features/apps/pages/onboarding-dialog.component.html
  7. 78
      src/Squidex/app/features/apps/pages/onboarding-dialog.component.scss
  8. 33
      src/Squidex/app/features/apps/pages/onboarding-dialog.component.ts
  9. 24
      src/Squidex/app/framework/angular/animations.ts
  10. 7
      src/Squidex/app/framework/angular/modal-view.directive.ts
  11. 70
      src/Squidex/app/framework/services/onboarding.service.spec.ts
  12. 6
      src/Squidex/app/theme/_bootstrap.scss
  13. 2
      src/Squidex/app/theme/_vars.scss
  14. BIN
      src/Squidex/wwwroot/images/logo-white-small.png
  15. BIN
      src/Squidex/wwwroot/images/onboarding-background.png
  16. BIN
      src/Squidex/wwwroot/images/onboarding-step1.png
  17. BIN
      src/Squidex/wwwroot/images/onboarding-step2.png
  18. BIN
      src/Squidex/wwwroot/images/onboarding-step3.png
  19. BIN
      src/Squidex/wwwroot/images/onboarding-step4.png

4
README.md

@ -4,7 +4,9 @@
Squidex is an open source headless CMS and content management hub. In contrast to a traditional CMS Squidex provides a rich API with OData filter and Swagger definitions. It is up to you to build your UI on top of it. It can be website, a native app or just another server. We build it with ASP.NET Core and CQRS and is tested for Windows and Linux on modern browsers. Squidex is an open source headless CMS and content management hub. In contrast to a traditional CMS Squidex provides a rich API with OData filter and Swagger definitions. It is up to you to build your UI on top of it. It can be website, a native app or just another server. We build it with ASP.NET Core and CQRS and is tested for Windows and Linux on modern browsers.
[![Gitter](https://img.shields.io/gitter/room/nwjs/nw.js.svg?style=square)](https://gitter.im/squidex-cms/Lobby) [![Build Status](http://build.squidex.io/api/badges/Squidex/squidex/status.svg)](http://build.squidex.io/Squidex/squidex) [![Gitter](https://img.shields.io/gitter/room/nwjs/nw.js.svg?style=square)](https://gitter.im/squidex-cms/Lobby)
[![Slack](https://img.shields.io/badge/chat-on_slack-E01765.svg?style=square)](https://squidex.slack.com/signup)
[![Build Status](http://build.squidex.io/api/badges/Squidex/squidex/status.svg)](http://build.squidex.io/Squidex/squidex)
Read the docs at [https://docs.squidex.io/](https://docs.squidex.io/) (work in progress) or just check out the code and play around. Read the docs at [https://docs.squidex.io/](https://docs.squidex.io/) (work in progress) or just check out the code and play around.

3
src/Squidex/app/features/apps/declarations.ts

@ -5,4 +5,5 @@
* Copyright (c) Sebastian Stehle. All rights reserved * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
export * from './pages/apps-page.component'; export * from './pages/apps-page.component';
export * from './pages/onboarding-dialog.component';

6
src/Squidex/app/features/apps/module.ts

@ -11,7 +11,8 @@ import { RouterModule, Routes } from '@angular/router';
import { SqxFrameworkModule, SqxSharedModule } from 'shared'; import { SqxFrameworkModule, SqxSharedModule } from 'shared';
import { import {
AppsPageComponent AppsPageComponent,
OnboardingDialogComponent
} from './declarations'; } from './declarations';
const routes: Routes = [ const routes: Routes = [
@ -28,7 +29,8 @@ const routes: Routes = [
RouterModule.forChild(routes) RouterModule.forChild(routes)
], ],
declarations: [ declarations: [
AppsPageComponent AppsPageComponent,
OnboardingDialogComponent
] ]
}) })
export class SqxFeatureAppsModule { } export class SqxFeatureAppsModule { }

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

@ -35,4 +35,6 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<sqx-onboarding-dialog [modalView]="onboardingModal"></sqx-onboarding-dialog>

29
src/Squidex/app/features/apps/pages/apps-page.component.ts

@ -5,12 +5,14 @@
* 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 { Subscription } from 'rxjs';
import { import {
AppsStoreService, AppsStoreService,
fadeAnimation, fadeAnimation,
ModalView ModalView,
OnboardingService
} from 'shared'; } from 'shared';
@Component({ @Component({
@ -21,17 +23,34 @@ import {
fadeAnimation fadeAnimation
] ]
}) })
export class AppsPageComponent implements OnInit { export class AppsPageComponent implements OnDestroy, OnInit {
public addAppDialog = new ModalView(); private onboardingAppsSubscription: Subscription;
public addAppDialog = new ModalView();
public apps = this.appsStore.apps; public apps = this.appsStore.apps;
public onboardingModal = new ModalView();
constructor( constructor(
private readonly appsStore: AppsStoreService private readonly appsStore: AppsStoreService,
private readonly onboardingService: OnboardingService
) { ) {
} }
public ngOnDestroy() {
this.onboardingAppsSubscription.unsubscribe();
}
public ngOnInit() { public ngOnInit() {
this.appsStore.selectApp(null); this.appsStore.selectApp(null);
this.onboardingAppsSubscription =
this.appsStore.apps
.subscribe(apps => {
if (apps.length === 0 && this.onboardingService.shouldShow('dialog')) {
this.onboardingService.disable('dialog');
this.onboardingModal.show();
}
});
} }
} }

154
src/Squidex/app/features/apps/pages/onboarding-dialog.component.html

@ -0,0 +1,154 @@
<div class="modal" *sqxModalView="modalView;onRoot:true;closeAuto:false" @fade>
<div class="modal-backdrop"></div>
<div class="modal-dialog">
<div class="modal-content">
<a class="header-right modal-close" (click)="modalView.hide()">Skip Tour</a>
<div class="onboarding-step" *ngIf="step === 0">
<img @fade class="header-left" src="/images/logo-white-small.png" />
<div class="onboarding-enter-leave" @slide>
<h1>Welcome to <span class="header-focus">Squidex CMS</span></h1>
<p>
You can start managing and distributing your content right away, but we we'd like to walk you through some basics first...
</p>
<p>
How's that?
</p>
<button (click)="next()" class="btn btn-success">Let'ts take a look around</button>
</div>
</div>
<div class="onboarding-step" *ngIf="step === 1">
<h1 class="header-left header-focus" @fade>Apps</h1>
<div @slide>
<div class="row">
<div class="col">
<div class="onboarding-text">
<p>
An App is the repository for your project, e.g. (blog, webshop or mobile app). You can assign contributors to your app to
work together.
</p>
<p>
You can create an unlimited number of Apps in Squidex to manage multiple projects at the same time.
</p>
</div>
</div>
<div class="col col-auto">
<img src="/images/onboarding-step1.png" />
</div>
</div>
<div class="footer">
<button (click)="next()" class="btn btn-success">Continue</button>
</div>
</div>
</div>
<div class="onboarding-step" *ngIf="step === 2">
<h1 class="header-left header-focus" @fade>Schemas</h1>
<div @slide>
<div class="row">
<div class="col">
<div class="onboarding-text">
<p>
Schemas define the structure of your content, the fields and the data types of a content item.
</p>
<p>
Beforew you can add content to your schema, make sure to hit the 'Publish' button at the top to make the schema availabel
to your content editors.
</p>
</div>
</div>
<div class="col col-auto">
<img src="/images/onboarding-step2.png" />
</div>
</div>
<div class="footer">
<button (click)="next()" class="btn btn-success">Keep going!</button>
</div>
</div>
</div>
<div class="onboarding-step" *ngIf="step === 3">
<h1 @fade class="header-left header-focus">Contents</h1>
<div @slide>
<div class="row">
<div class="col">
<div class="onboarding-text">
<p>
Content is the actual data in your app which is grouped by the schema.
</p>
<p>
Select a published schema first, then add content for this schema.
</p>
</div>
</div>
<div class="col col-auto">
<img src="/images/onboarding-step3.png" />
</div>
</div>
<div class="footer">
<button (click)="next()" class="btn btn-success">Almost there!</button>
</div>
</div>
</div>
<div class="onboarding-step" *ngIf="step === 4">
<h1 @fade class="header-left header-focus">Assets</h1>
<div @slide>
<div class="row">
<div class="col">
<div class="onboarding-text">
<p>
The assets contains all files that can also be linked to your content. For example images, videos or documents.
</p>
<p>
You can upload the assets here and use them later or also upload them directly when you create a new content item with an
asset field.
</p>
</div>
</div>
<div class="col col-auto">
<img src="/images/onboarding-step4.png" />
</div>
</div>
<div class="footer">
<button (click)="next()" class="btn btn-success">Got It!</button>
</div>
</div>
</div>
<div class="onboarding-step" *ngIf="step === 5">
<img @fade class="header-left" src="/images/logo-white-small.png" />
<div class="onboarding-enter-leave" @slide>
<h1>Awesome, now you know the basics!</h1>
<p>
But that's not all of the support we can provide. <br />You can go to <a href="https://docs.squidex.io/"
target="_blank">https://docs.squidex.io/</a> to read more.
</p>
<p>
Do you want to join our community?
</p>
<div>
<a class="btn btn-success" href="https://squidex.slack.com/signup" target="_blank">
Join us on Slack
</a> &nbsp;
<a class="btn btn-success" href="https://github.com/squidex/squidex" target="_blank">
Join us on Github
</a>
</div>
</div>
</div>
</div>
</div>
</div>

78
src/Squidex/app/features/apps/pages/onboarding-dialog.component.scss

@ -0,0 +1,78 @@
@import '_vars';
@import '_mixins';
$size-width: 825px;
$size-height: 576px;
$size-image: 476px;
h1 {
font-size: 1.6rem;
}
p {
line-height: 1.8rem;
}
.modal {
&-content,
&-dialog {
min-height: $size-height;
max-height: $size-height;
min-width: $size-width;
max-width: $size-width;
}
&-content {
color: $color-dark-foreground;
background-color: $color-dark-onboarding;
background-image: url('/images/onboarding-background.png');
overflow: hidden;
position: relative;
}
&-close {
text-decoration: underline !important;
cursor: pointer;
color: $color-dark-foreground;
}
}
.header-focus {
color: $color-theme-blue;
}
.header-left {
@include absolute(-70px, auto, auto, 2rem);
}
.header-right {
@include absolute(2rem, 2rem, auto, auto);
}
.footer {
@include absolute(auto, auto, 5rem, 2rem);
}
.onboarding {
&-enter-leave {
& {
text-align: center;
margin: 4rem auto 0;
max-width: 28rem;
min-width: 28rem;
}
p {
margin: 2rem 0;
}
}
&-text {
padding-left: 2rem;
}
&-step {
@include absolute($size-height - $size-image, 0, 0, 0);
}
}

33
src/Squidex/app/features/apps/pages/onboarding-dialog.component.ts

@ -0,0 +1,33 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Component, Input } from '@angular/core';
import {
fadeAnimation,
ModalView,
slideAnimation
} from 'framework';
@Component({
selector: 'sqx-onboarding-dialog',
styleUrls: ['./onboarding-dialog.component.scss'],
templateUrl: './onboarding-dialog.component.html',
animations: [
fadeAnimation, slideAnimation
]
})
export class OnboardingDialogComponent {
public step = 0;
@Input()
public modalView = new ModalView();
public next() {
this.step = this.step + 1;
}
}

24
src/Squidex/app/framework/angular/animations.ts

@ -37,6 +37,29 @@ export function buildSlideRightAnimation(name = 'slideRight', timing = '150ms'):
); );
} }
export function buildSlideAnimation(name = 'slide', timing = '400ms'): AnimationEntryMetadata {
return trigger(
name, [
transition(':enter', [
style({ transform: 'translateX(100%)' }),
animate(timing, style({ transform: 'translateX(0%)' }))
]),
transition(':leave', [
style({transform: 'translateX(0%)' }),
animate(timing, style({ transform: 'translateX(-100%)' }))
]),
state('true',
style({ transform: 'translateX(0%)' })
),
state('false',
style({ transform: 'translateX(-100%)' })
),
transition('1 => 0', animate(timing)),
transition('0 => 1', animate(timing))
]
);
}
export function buildFadeAnimation(name = 'fade', timing = '150ms'): AnimationEntryMetadata { export function buildFadeAnimation(name = 'fade', timing = '150ms'): AnimationEntryMetadata {
return trigger( return trigger(
name, [ name, [
@ -85,4 +108,5 @@ export function buildHeightAnimation(name = 'height', timing = '200ms'): Animati
export const fadeAnimation = buildFadeAnimation(); export const fadeAnimation = buildFadeAnimation();
export const heightAnimation = buildHeightAnimation(); export const heightAnimation = buildHeightAnimation();
export const slideAnimation = buildSlideAnimation();
export const slideRightAnimation = buildSlideRightAnimation(); export const slideRightAnimation = buildSlideRightAnimation();

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

@ -26,6 +26,9 @@ export class ModalViewDirective implements OnChanges, OnDestroy {
@Input('sqxModalViewOnRoot') @Input('sqxModalViewOnRoot')
public placeOnRoot = false; public placeOnRoot = false;
@Input('sqxModalViewCloseAuto')
public closeAuto = true;
constructor( constructor(
private readonly templateRef: TemplateRef<any>, private readonly templateRef: TemplateRef<any>,
private readonly renderer: Renderer, private readonly renderer: Renderer,
@ -84,6 +87,10 @@ export class ModalViewDirective implements OnChanges, OnDestroy {
} }
private startListening() { private startListening() {
if (!this.closeAuto) {
return;
}
this.documentClickListener = 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) {

70
src/Squidex/app/framework/services/onboarding.service.spec.ts

@ -0,0 +1,70 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { OnboardingService, OnboardingServiceFactory } from './../';
class LocalStoreMock {
private store = {};
public get(key: string) {
return this.store[key];
}
public set(key: string, value: string) {
this.store[key] = value;
}
}
describe('OnboardingService', () => {
const localStore = new LocalStoreMock();
it('should instantiate from factory', () => {
const onboardingService = OnboardingServiceFactory(<any>localStore);
expect(onboardingService).toBeDefined();
});
it('should instantiate', () => {
const onboardingService = new OnboardingService(<any>localStore);
expect(onboardingService).toBeDefined();
});
it('should return true when value not in store', () => {
localStore.set('squidex.onboarding.disable.feature1', '0');
const onboardingService = new OnboardingService(<any>localStore);
onboardingService.disable('feature2');
expect(onboardingService.shouldShow('feature1')).toBeTruthy();
});
it('should return false when value in store', () => {
localStore.set('squidex.onboarding.disable.feature1', '1');
const onboardingService = new OnboardingService(<any>localStore);
expect(onboardingService.shouldShow('feature1')).toBeFalsy();
});
it('should return false when disabled', () => {
const onboardingService = new OnboardingService(<any>localStore);
onboardingService.disable('feature1');
expect(onboardingService.shouldShow('feature1')).toBeFalsy();
});
it('should return false when all disabled', () => {
const onboardingService = new OnboardingService(<any>localStore);
onboardingService.disableAll();
expect(onboardingService.shouldShow('feature1')).toBeFalsy();
});
});

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

@ -71,6 +71,12 @@ a {
@include opacity(.8); @include opacity(.8);
pointer-events: none; pointer-events: none;
} }
&.btn {
&:focus {
color: inherit;
}
}
} }
// //

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

@ -53,6 +53,8 @@ $color-dark2-control: #2e3842;
$color-dark2-separator: #2e3842; $color-dark2-separator: #2e3842;
$color-dark2-placeholder: #757e8d; $color-dark2-placeholder: #757e8d;
$color-dark-onboarding: #2d333c;
$color-panel-icon: #a2b0b6; $color-panel-icon: #a2b0b6;
$color-badge-success-background: #e4f6e6; $color-badge-success-background: #e4f6e6;

BIN
src/Squidex/wwwroot/images/logo-white-small.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
src/Squidex/wwwroot/images/onboarding-background.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

BIN
src/Squidex/wwwroot/images/onboarding-step1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
src/Squidex/wwwroot/images/onboarding-step2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
src/Squidex/wwwroot/images/onboarding-step3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
src/Squidex/wwwroot/images/onboarding-step4.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Loading…
Cancel
Save