Browse Source

Onboarding Dialog.

pull/131/head
Sebastian Stehle 8 years ago
parent
commit
587dba2a05
  1. 4
      README.md
  2. 1
      src/Squidex/app/features/apps/declarations.ts
  3. 6
      src/Squidex/app/features/apps/module.ts
  4. 2
      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.
[![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.

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

@ -6,3 +6,4 @@
*/
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 {
AppsPageComponent
AppsPageComponent,
OnboardingDialogComponent
} from './declarations';
const routes: Routes = [
@ -28,7 +29,8 @@ const routes: Routes = [
RouterModule.forChild(routes)
],
declarations: [
AppsPageComponent
AppsPageComponent,
OnboardingDialogComponent
]
})
export class SqxFeatureAppsModule { }

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

@ -36,3 +36,5 @@
</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
*/
import { Component, OnInit } from '@angular/core';
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Subscription } from 'rxjs';
import {
AppsStoreService,
fadeAnimation,
ModalView
ModalView,
OnboardingService
} from 'shared';
@Component({
@ -21,17 +23,34 @@ import {
fadeAnimation
]
})
export class AppsPageComponent implements OnInit {
public addAppDialog = new ModalView();
export class AppsPageComponent implements OnDestroy, OnInit {
private onboardingAppsSubscription: Subscription;
public addAppDialog = new ModalView();
public apps = this.appsStore.apps;
public onboardingModal = new ModalView();
constructor(
private readonly appsStore: AppsStoreService
private readonly appsStore: AppsStoreService,
private readonly onboardingService: OnboardingService
) {
}
public ngOnDestroy() {
this.onboardingAppsSubscription.unsubscribe();
}
public ngOnInit() {
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 {
return trigger(
name, [
@ -85,4 +108,5 @@ export function buildHeightAnimation(name = 'height', timing = '200ms'): Animati
export const fadeAnimation = buildFadeAnimation();
export const heightAnimation = buildHeightAnimation();
export const slideAnimation = buildSlideAnimation();
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')
public placeOnRoot = false;
@Input('sqxModalViewCloseAuto')
public closeAuto = true;
constructor(
private readonly templateRef: TemplateRef<any>,
private readonly renderer: Renderer,
@ -84,6 +87,10 @@ export class ModalViewDirective implements OnChanges, OnDestroy {
}
private startListening() {
if (!this.closeAuto) {
return;
}
this.documentClickListener =
this.renderer.listenGlobal('document', 'click', (event: MouseEvent) => {
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);
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-placeholder: #757e8d;
$color-dark-onboarding: #2d333c;
$color-panel-icon: #a2b0b6;
$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