demo shell (ng2)

basic angular2-based demo shell
This commit is contained in:
Denys Vuika
2016-04-06 14:35:46 +01:00
commit f9e1ad80a9
41 changed files with 1803 additions and 0 deletions

7
demo-shell-ng2/.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
typings/
node_modules/
*.js
*.js.map
.idea
!app/widgets/*.js
!app/js/*.js

21
demo-shell-ng2/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Alfresco
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

21
demo-shell-ng2/README.md Normal file
View File

@@ -0,0 +1,21 @@
### Start development
Install the npm packages described in the `package.json` and verify that it works:
```bash
$ npm install
$ npm start
```
You're ready to write your application.
Remember the npm scripts in `package.json`:
* `npm start` - runs the compiler and a server at the same time, both in "watch mode".
* `npm run tsc` - runs the TypeScript compiler once.
* `npm run tsc:w` - runs the TypeScript compiler in watch mode; the process keeps running, awaiting changes to TypeScript files and re-compiling when it sees them.
* `npm run lite` - runs the [lite-server](https://www.npmjs.com/package/lite-server), a light-weight, static file server, written and maintained by
[John Papa](https://github.com/johnpapa) and
[Christopher Martin](https://github.com/cgmartin)
with excellent support for Angular apps that use routing.
* `npm run typings` - runs the typings tool.
* `npm run postinstall` - called by *npm* automatically *after* it successfully completes package installation. This script installs the TypeScript definition files this app requires.

View File

@@ -0,0 +1,109 @@
<side-menu #elementsMenu title="Elements">
<form-design-toolbar></form-design-toolbar>
</side-menu>
<side-menu #actionMenu title="Actions">
<div class="p-10">
<button type="button" class="btn btn-success btn-block">
Upload <i class="fa fa-plus"></i>
</button>
<button type="button" class="btn btn-default btn-block">
Folder <i class="fa fa-plus"></i>
</button>
</div>
</side-menu>
<side-menu #propertiesMenu title="Properties" direction="right">
<a href="#">Property 1</a>
<a href="#">Property 2</a>
<a href="#">Property 3</a>
<a href="#">Property 4</a>
<a href="#">Property 5</a>
<a href="#">Property 6</a>
</side-menu>
<app-navbar>
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand navbar-alfresco-logo" [routerLink]="['Home']">
<img src="app/img/blank.gif">
</a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li [class.active]="actionMenu.isOpen">
<a href="#" (click)="toggleMenu(actionMenu, $event)" class="image-button">
<i class="fa fa-tasks fa-2x"></i>
</a>
</li>
<li [class.active]="isActive(['Home'])">
<a [routerLink]="['Home']">Home <span class="sr-only">(current)</span></a>
</li>
<!--
<li [class.active]="isActive(['Child1', {id: 1}])">
<a [routerLink]="['Child1', {id: 1}]">Child 1</a>
</li>
<li [class.active]="isActive(['Child2'])">
<a [routerLink]="['Child2']">Child 2</a>
</li>
-->
<li [class.active]="isActive(['Page1'])">
<a [routerLink]="['Page1']">Page 1</a>
</li>
<li [class.active]="isActive(['Page2'])">
<a [routerLink]="['Page2']">Page 2</a>
</li>
<li [class.active]="isActive(['Forms'])">
<a [routerLink]="['Forms']">Forms</a>
</li>
</ul>
<form class="navbar-form navbar-left" role="search">
<div class="form-group">
<input type="text" class="form-control" placeholder="Search files and folders">
</div>
<button type="submit" class="btn btn-default">
<i class="fa fa-search"></i>
</button>
</form>
<ul class="nav navbar-nav navbar-right">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
<span class="glyphicon glyphicon-menu-hamburger"></span>
</a>
<ul class="dropdown-menu">
<li><a href="#">Action</a></li>
<li><a href="#">Another action</a></li>
<li><a href="#">Something else here</a></li>
<li role="separator" class="divider"></li>
<li><a *ngIf="!isLoggedIn()" [routerLink]="['Login']">Login</a></li>
<li><a *ngIf="isLoggedIn()" href="#" (click)="onLogout($event)">Logout</a></li>
</ul>
</li>
</ul>
<ul *ngIf="isActive(['Forms'])" class="nav navbar-nav navbar-right">
<li [class.active]="elementsMenu.isOpen">
<a href="#" (click)="toggleMenu(elementsMenu, $event)" class="image-button">
<i class="fa fa-cubes fa-2x"></i>
</a>
</li>
<li [class.active]="propertiesMenu.isOpen">
<a href="#" (click)="toggleMenu(propertiesMenu, $event)" class="image-button">
<i class="fa fa-edit fa-2x"></i>
</a>
</li>
</ul>
</div>
</app-navbar>
<!--<router-outlet></router-outlet>-->
<auth-router-outlet></auth-router-outlet>
<!-- Caches -->
<div id="drag-images-cache"></div>

View File

@@ -0,0 +1,67 @@
import {Component} from 'angular2/core';
import {FormService} from './services/form-service';
import {Router, RouteConfig, ROUTER_DIRECTIVES} from "angular2/router";
import {Login} from "./components/login";
import {Authentication} from "./services/authentication";
import {AuthRouterOutlet} from "./components/AuthRouterOutlet";
import {SideMenu} from "./components/core/SideMenu";
import {AppNavBar} from "./components/core/navbar.component";
import {FormDesignToolbar} from "./components/form-design-toolbar.component";
import {HomeView} from "./components/home.view";
import {FormsView} from "./components/forms.view";
import {Page1View} from "./components/page1.view";
import {Page2View} from "./components/page2.view";
@Component({
selector: 'my-app',
templateUrl: 'app/app.component.html',
directives: [ROUTER_DIRECTIVES, AuthRouterOutlet, SideMenu, AppNavBar, FormDesignToolbar],
providers: [FormService]
})
@RouteConfig([
{path: '/', name: 'Home', component: HomeView, useAsDefault: true},
{path: '/login', name: 'Login', component: Login},
{path: '/forms', name: 'Forms', component: FormsView},
{path: '/page1', name: 'Page1', component: Page1View},
{path: '/page2', name: 'Page2', component: Page2View}
])
export class AppComponent {
constructor(
public auth: Authentication,
public router: Router
){}
toggleMenu(menu: SideMenu, $event) {
if (menu) {
menu.toggle();
}
if ($event) {
$event.preventDefault();
}
}
isActive(instruction: any[]): boolean {
return this.router.isRouteActive(this.router.generate(instruction));
}
isLoggedIn(): boolean {
return this.auth.isLoggedIn();
}
onLogout(event) {
event.preventDefault();
this.auth.logout()
.subscribe(
() => this.router.navigate(['Login'])
);
}
/*
hideMenu(menu: SideMenu) {
if (menu && menu.isOpen) {
menu.close();
}
}
*/
}

View File

@@ -0,0 +1,36 @@
import { ElementRef, DynamicComponentLoader, Directive, Attribute } from 'angular2/core';
import { Router, RouterOutlet, ComponentInstruction } from 'angular2/router';
import {Authentication} from '../services/authentication';
@Directive({selector: 'auth-router-outlet'})
export class AuthRouterOutlet extends RouterOutlet {
publicRoutes: Array<string>;
private router: Router;
constructor(
_elementRef: ElementRef, _loader: DynamicComponentLoader,
_parentRouter: Router, @Attribute('name') nameAttr: string,
private authentication: Authentication
) {
super(_elementRef, _loader, _parentRouter, nameAttr);
this.router = _parentRouter;
this.publicRoutes = [
'', 'login', 'signup'
];
}
activate(instruction: ComponentInstruction) {
if (this._canActivate(instruction.urlPath)) {
return super.activate(instruction);
}
this.router.navigate(['Login']);
}
_canActivate(url) {
return this.publicRoutes.indexOf(url) !== -1
|| this.authentication.isLoggedIn();
}
}

View File

@@ -0,0 +1,47 @@
import {Component, Input, ElementRef} from "angular2/core";
@Component({
selector: 'side-menu',
host: {
'(click)': 'onClick($event)',
//'(document:click)': 'onOutsideClick($event)'
},
template: `
<nav class="cbp-spmenu cbp-spmenu-vertical cbp-spmenu-{{direction}} inline-menu"
[ngClass]="{ 'cbp-spmenu-open': isOpen }">
<h3>
{{title}}
<a href="#" class="menu-close pull-right" (click)="close()">
<i class="glyphicon glyphicon-remove"></i>
</a>
</h3>
<ng-content></ng-content>
</nav>
`
})
export class SideMenu {
@Input() title: string = '';
@Input() direction: string = 'left';
isOpen: boolean = false;
constructor(private el: ElementRef) {
}
onClick(event) {
event.preventDefault();
event.stopPropagation();
}
toggle() {
this.isOpen = !this.isOpen;
}
open() {
this.isOpen = true;
}
close() {
this.isOpen = false;
}
}

View File

@@ -0,0 +1,24 @@
import {Component} from "angular2/core";
@Component({
selector: 'app-navbar',
template: `
<nav class="navbar navbar-default navbar-fixed-top">
<div class="container-fluid">
<ng-content></ng-content>
</div>
</nav>
`,
styles: [
`
:host .image-button {
padding-bottom: 10px;
padding-top: 12px;
max-height: 50px;
}
`
]
})
export class AppNavBar {
}

View File

@@ -0,0 +1,51 @@
import {Component, Input} from 'angular2/core';
@Component({
selector: 'tabs',
template: `
<ul class="nav nav-tabs">
<li *ngFor="#tab of tabs" (click)="selectTab(tab, $event)" [class.active]="tab.active">
<a href="#">{{tab.title}}</a>
</li>
</ul>
<ng-content></ng-content>
`
})
export class Tabs {
tabs: Tab[] = [];
selectTab(tab:Tab, $event) {
this.tabs.forEach(tab => {
tab.active = false;
});
tab.active = true;
if ($event) {
$event.preventDefault();
}
}
addTab(tab:Tab) {
if (this.tabs.length === 0) {
tab.active = true;
}
this.tabs.push(tab);
}
}
@Component({
selector: 'tab',
template: `
<div [hidden]="!active">
<ng-content></ng-content>
</div>
`
})
export class Tab {
@Input('tabTitle') title;
active: Boolean;
constructor(tabs: Tabs) {
tabs.addTab(this);
}
}

View File

@@ -0,0 +1,96 @@
import {Component, ElementRef, OnInit} from "angular2/core";
declare var dropZone: any;
declare var widgets: any;
@Component({
selector: 'form-design-surface',
template: '<div></div>'
})
export class FormDesignSurface implements OnInit {
private _selectedWidget: Element;
constructor(public elementRef: ElementRef) {
//el.nativeElement.style.backgroundColor = 'yellow';
}
get selectedWidget(): Element {
return this._selectedWidget;
}
set selectedWidget(val: Element) {
if (this.selectedWidget && this.selectedWidget != val) {
this._selectedWidget.classList.remove('selected');
}
this._selectedWidget = val;
if (this._selectedWidget) {
this._selectedWidget.classList.add('selected');
}
}
ngOnInit() {
// Create root container
var container = widgets.container.create();
container.dataset.widgetType = 'container';
this.setupWidget(container);
this.elementRef.nativeElement.appendChild(container);
}
private setupWidget(widget: HTMLElement) {
// initialize all drop placeholders
var dropPlaceholders = widget.querySelectorAll('.drop-zone');
for (var i = 0; i < dropPlaceholders.length; i++) {
var placeholder = dropPlaceholders[i];
var z = new dropZone({
element: placeholder,
onDrop: this.onWidgetDrop.bind(this)
});
}
// initialize clicks
if (widget.dataset['widgetId']) {
widget.addEventListener('mouseup', this.onWidgetMouseUp.bind(this), false);
}
// wire child element clicks
var nested = widget.querySelectorAll('[data-widget-id]');
for (var x = 0; x < nested.length; x++) {
nested[x].addEventListener('mouseup', this.onWidgetMouseUp.bind(this), false);
}
}
private onWidgetMouseUp(e) {
var wid = e.currentTarget.dataset.widgetId;
if (wid) {
console.log('Selected Widget Id: ' + wid);
this.selectedWidget = e.currentTarget;
e.stopPropagation();
}
}
private onWidgetDrop(dz, opts) {
var widgetType = opts.widgetType;
if (widgetType) {
var component = widgets[widgetType];
if (component) {
var widget = component.create();
if (widget) {
widget.dataset.widgetType = widgetType;
this.setupWidget(widget);
// insert widget before drop zone
var container = dz.parentElement;
container.insertBefore(widget, dz);
// create new drop zone
var zone = new dropZone({
onDrop: this.onWidgetDrop.bind(this),
minHeight: '5px'
});
// insert new drop zone before widget
container.insertBefore(zone.element, widget);
}
}
}
}
}

View File

@@ -0,0 +1,91 @@
import {Component, OnInit} from "angular2/core";
import {FormService} from "../services/form-service";
@Component({
selector: 'form-design-toolbar',
providers: [FormService],
//encapsulation: ViewEncapsulation.Native,
styles: [`
.category-header {
color: #555;
padding: 11px;
margin: 0;
background: #e7e7e7;
cursor: default;
font-size: 14px;
font-weight: bold;
}
a.toolbar-item {
cursor: move;
display: block;
color: #777;
font-size: 1.1em;
font-weight: 300;
text-decoration: none;
border-bottom: 1px solid #e7e7e7;
padding: 1em;
}
a.toolbar-item:hover {
color: #555;
background: #f8f8f8;
}
a.toolbar-item:active, a.toolbar-item.active {
color: #fff;
border-color: #428bca;
background-color: #428bca;
}
`],
template: `
<template ngFor #category [ngForOf]="categories">
<h3 class="category-header">{{category.name}}</h3>
<a *ngFor="#widget of category.widgets" class="toolbar-item" attr.data-widget-type="{{widget.type}}" draggable="true"
(dragstart)="onElementDragStart($event)" (dragend)="onElementDragEnd($event)">
<i class="{{widget.iconClass || 'fa fa-puzzle-piece'}}"></i> {{widget.name}}
</a>
</template>
`
})
export class FormDesignToolbar implements OnInit {
categories: any[];
private dragCache;
constructor(
private _formService: FormService
) {}
ngOnInit() {
this.categories = this._formService.getWidgetCategories();
// Stores drag ghost elements
this.dragCache = document.getElementById('drag-images-cache');
}
onElementDragStart(e) {
e.dataTransfer.effectAllowed = 'move';
var widgetType = e.target.dataset.widgetType;
var payload = { "widgetType": widgetType };
e.dataTransfer.setData('text', JSON.stringify(payload));
//var dragImage = getDragImage(widgetType);
var dragImage = this._formService.getDragImage(widgetType);
this.dragCache.appendChild(dragImage);
//e.dataTransfer.setDragImage(dragImage, dragImage.offsetWidth / 2, 0);
e.dataTransfer.setDragImage(dragImage, 0, 0);
e.stopPropagation();
}
onElementDragEnd(e) {
if (this.dragCache) {
this.dragCache.innerHTML = '';
}
}
}

View File

@@ -0,0 +1,23 @@
import {Component} from 'angular2/core';
import {FormDesignSurface} from "./form-design-surface.component";
@Component({
selector: 'forms-view',
template: `
<div class="container" style="width:970px;">
<div class="row">
<div class="col-md-12">
<!-- Design surface -->
<form-design-surface></form-design-surface>
</div>
</div>
</div>
`,
directives: [FormDesignSurface]
})
export class FormsView {
/*
@ViewChild(FormDesignSurface)
private _surface: FormDesignSurface;
*/
}

View File

@@ -0,0 +1,17 @@
import {Component} from 'angular2/core';
@Component({
selector: 'home-view',
template: `
<div class="container">
<div class="row">
<div class="col-md-12">
<h1>Home View</h1>
</div>
</div>
</div>
`,
directives: []
})
export class HomeView {
}

View File

@@ -0,0 +1,48 @@
import {Component} from "angular2/core";
import {Router, ROUTER_DIRECTIVES} from "angular2/router";
import {FORM_DIRECTIVES, ControlGroup, FormBuilder, Validators} from "angular2/common";
import {Authentication} from "../services/authentication";
@Component({
selector: 'login',
directives: [ROUTER_DIRECTIVES, FORM_DIRECTIVES],
template: `
<div class="row">
<div class="col-md-4 col-md-offset-4">
<form [ngFormModel]="form" (submit)="onSubmit(form.value, $event)">
<div *ngIf="error">Check your password</div>
<div class="form-group">
<label for="username">Username</label>
<input class="form-control" type="text" ngControl="username">
</div>
<div class="form-group">
<label for="password">Password</label>
<input class="form-control" type="password" ngControl="password">
</div>
<button type="submit" class="btn btn-default" [disabled]="!form.valid">Login</button>
</form>
</div>
</div>
`
})
export class Login {
form: ControlGroup;
error: boolean = false;
constructor(fb: FormBuilder, public auth: Authentication, public router: Router) {
this.form = fb.group({
username: ['', Validators.required],
password: ['', Validators.required]
});
}
onSubmit(value: any, event) {
//event.preventDefault();
this.auth.login(value.username, value.password)
.subscribe(
//(token: any) => this.router.navigate(['../Home']),
(token: any) => this.router.navigate(['Home']),
() => { this.error = true; }
);
}
}

View File

@@ -0,0 +1,14 @@
import {Component} from "angular2/core";
@Component({
selector: 'page1-view',
template: `
<div class="container">
<div class="row">
<h1>Page 1</h1>
</div>
</div>
`
})
export class Page1View {
}

View File

@@ -0,0 +1,14 @@
import {Component} from "angular2/core";
@Component({
selector: 'page2-view',
template: `
<div class="container">
<div class="row">
<h1>Page 2</h1>
</div>
</div>
`
})
export class Page2View {
}

View File

@@ -0,0 +1,7 @@
body { padding-top: 70px; }
/* Utils */
.p-10 {
padding: 10px;
}

View File

@@ -0,0 +1,116 @@
/* Generated by less 2.2.0 */
.drag-image {
left: 0;
position: absolute;
top: 0;
z-index: -1;
}
.drop-zone {
border-radius: 4px;
visibility: visible;
}
.drop-zone.over {
outline: 1px dashed #ddd;
}
.widget-button {
cursor: default;
}
.widget-button[contenteditable="true"] {
cursor: text;
}
.widget-input:read-only {
background-color: #ffffff;
cursor: default;
}
.widget-row {
background-color: #F5F5F5;
border: 1px solid #dddddd;
border-radius: 4px;
margin: 5px 0;
padding: 25px 14px 0;
position: relative;
}
.widget-row:before {
background-color: #F5F5F5;
border: 1px solid #dddddd;
border-radius: 4px 0 4px 0;
color: #9DA0A4;
content: "Row";
font-size: 12px;
font-weight: bold;
left: -1px;
padding: 3px 7px;
position: absolute;
top: -1px;
}
.widget-row.selected {
border-color: green;
transition: border-color 0.5s;
}
.widget-row.selected:before {
background-color: green;
border-color: green;
color: white;
}
.widget-col {
background-color: #ffffff;
border: 1px solid #dddddd;
border-radius: 4px;
margin: 15px 0;
padding: 39px 19px 24px;
position: relative;
}
.widget-col:before {
background-color: #F5F5F5;
border: 1px solid #dddddd;
border-radius: 4px 0 4px 0;
color: #9DA0A4;
content: "Column";
font-size: 12px;
font-weight: bold;
left: -1px;
padding: 3px 7px;
position: absolute;
top: -1px;
}
.widget-col.selected {
border-color: green;
transition: border-color 0.5s;
}
.widget-col.selected:before {
background-color: green;
border-color: green;
color: white;
}
.widget-container {
border: 1px solid #dddddd;
border-radius: 4px;
margin-left: 0;
margin-top: 10px;
padding: 30px 15px 15px;
position: relative;
width: 100%;
word-wrap: break-word;
}
.widget-container:before {
background-color: #F5F5F5;
border: 1px solid #dddddd;
border-radius: 4px 0 4px 0;
color: #9DA0A4;
content: "Container";
font-size: 12px;
font-weight: bold;
left: -1px;
padding: 3px 7px;
position: absolute;
top: -1px;
}
.widget-container.selected {
border-color: green;
transition: border-color 0.5s;
}
.widget-container.selected:before {
background-color: green;
border-color: green;
color: white;
}

View File

@@ -0,0 +1,201 @@
/* General styles for all menus */
.cbp-spmenu {
background-color: #fff;
position: fixed;
border-color: #e7e7e7;
}
.cbp-spmenu > h3 {
color: #555;
font-size: 1.9em;
padding: 11px;
margin: 0;
font-weight: 300;
background: #e7e7e7;
cursor: default;
}
.cbp-spmenu > a {
display: block;
color: #777;
font-size: 1.1em;
font-weight: 300;
text-decoration: none;
}
.cbp-spmenu > a:hover {
color: #555;
background: #f8f8f8;
}
.cbp-spmenu > a:active,
.cbp-spmenu > a.active {
color: #fff;
border-color: #428bca;
background-color: #428bca;
}
/* Orientation-dependent styles for the content of the menu */
.cbp-spmenu-vertical {
width: 240px;
height: 100%;
top: 0;
/*z-index: 1000;*/
z-index: 1040; /* default TBS navbar index is 1030 */
overflow-y: auto; /* vertical scrollbar */
overflow-x: hidden;
}
.cbp-spmenu-vertical > a {
border-bottom: 1px solid #e7e7e7;
padding: 1em;
}
.cbp-spmenu-horizontal {
width: 100%;
height: 150px;
left: 0;
z-index: 1000;
overflow: hidden;
}
.cbp-spmenu-horizontal > h3 {
height: 100%;
width: 20%;
float: left;
}
.cbp-spmenu-horizontal > a {
float: left;
width: 20%;
padding: 0.8em;
border-left: 1px solid #258ecd;
}
/* Vertical menu that slides from the left or right */
.cbp-spmenu-left {
left: -240px;
border-right: 1px solid #e7e7e7;
}
.cbp-spmenu-right {
right: -240px;
border-left: 1px solid #e7e7e7;
}
.cbp-spmenu-left.cbp-spmenu-open {
left: 0;
}
.cbp-spmenu-right.cbp-spmenu-open {
right: 0;
}
/* Horizontal menu that slides from the top or bottom */
.cbp-spmenu-top {
top: -150px;
}
.cbp-spmenu-bottom {
bottom: -150px;
}
.cbp-spmenu-top.cbp-spmenu-open {
top: 0;
}
.cbp-spmenu-bottom.cbp-spmenu-open {
bottom: 0;
}
/* Push classes applied to the body */
.cbp-spmenu-push {
overflow-x: hidden;
position: relative;
left: 0;
}
.cbp-spmenu-push-toright {
left: 240px;
}
.cbp-spmenu-push-toleft {
left: -240px;
}
/* Transitions */
.cbp-spmenu,
.cbp-spmenu-push {
-webkit-transition: all 0.3s ease;
-moz-transition: all 0.3s ease;
transition: all 0.3s ease;
}
/* Example media queries */
@media screen and (max-width: 55.1875em){
.cbp-spmenu-horizontal {
font-size: 75%;
height: 110px;
}
.cbp-spmenu-top {
top: -110px;
}
.cbp-spmenu-bottom {
bottom: -110px;
}
}
@media screen and (max-height: 26.375em){
.cbp-spmenu-vertical {
font-size: 90%;
width: 190px;
}
.cbp-spmenu-left,
.cbp-spmenu-push-toleft {
left: -190px;
}
.cbp-spmenu-right {
right: -190px;
}
.cbp-spmenu-push-toright {
left: 190px;
}
}
/* Extra stuff */
/* move scrollbar for right menu to the right */
.cbp-spmenu-right { direction: rtl; }
.cbp-spmenu-right > * { direction: ltr; }
/* misc */
a.menu-close {
color: #555;
}
/* Show menu inside main content rather than page */
.inline-menu {
padding-bottom: 50px;
margin-top: 50px;
}
.inline-menu > h3 {
font-size: 14px;
font-weight: bold;
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,148 @@
/* Layout */
.drag-image {
position: absolute;
left: 0;
top: 0;
z-index: -1;
}
.drop-zone {
border-radius: 4px;
/*height: 20px;*/
visibility: visible /*!Important*/;
}
.drop-zone.over {
/*height: 100px;*/
outline: 1px dashed #ddd;
}
ul.toolbar {
list-style: none;
margin: 0 0 1em;
padding: 0;
}
ul.toolbar li {
background: #F7F7F7;
border: 1px solid #D9D9D9;
border-radius: 3px;
display: inline-block;
margin: 0 1em 1em 0;
padding: 0.5em 1em;
}
/* Widgets */
/*.widget {}*/
.widget.widget-button {
cursor: default;
}
.widget.widget-button[contentEditable="true"] {
cursor: text;
}
/*.widget.widget-input {}*/
.widget.widget-input:read-only {
background-color: #fff;
cursor: default;
}
/*.widget.widget-input.drag:before {
content: "Input";
}*/
.widget.widget-input:before {
content: "Input";
}
.widget.widget-row {
background-color: #F5F5F5;
border: 1px solid #DDDDDD;
border-radius: 4px;
margin: 5px 0;
padding: 25px 14px 0;
position: relative;
}
.widget.widget-row:before {
background-color: #F5F5F5;
border: 1px solid #DDDDDD;
border-radius: 4px 0 4px 0;
color: #9DA0A4;
content: "Row";
font-size: 12px;
font-weight: bold;
left: -1px;
line-height: 2;
padding: 3px 7px;
position: absolute;
top: -1px;
}
.widget.widget-col {
background-color: #FFFFFF;
border: 1px solid #DDDDDD;
border-radius: 4px;
margin: 15px 0;
padding: 39px 19px 24px;
position: relative;
}
.widget.widget-col:before {
background-color: #F5F5F5;
border: 1px solid #DDDDDD;
border-radius: 4px 0 4px 0;
color: #9DA0A4;
content: "Column";
font-size: 12px;
font-weight: bold;
left: -1px;
padding: 3px 7px;
position: absolute;
top: -1px;
}
.widget.widget-container {
margin-left: 0;
margin-top: 10px;
padding: 30px 15px 15px;
border: 1px solid #DDDDDD;
border-radius: 4px;
position: relative;
width: 100%;
word-wrap: break-word;
}
.widget.widget-container:before {
background-color: #F5F5F5;
border: 1px solid #DDDDDD;
border-radius: 4px 0 4px 0;
color: #9DA0A4;
content: "Container";
font-size: 12px;
font-weight: bold;
left: -1px;
padding: 3px 7px;
position: absolute;
top: -1px;
}
.widget.widget-row.selected,
.widget.widget-container.selected,
.widget.widget-col.selected {
border-color: green;
transition: border-color .5s;
}
.widget.widget-row.selected:before,
.widget.widget-container.selected:before,
.widget.widget-col.selected:before {
border-color: green;
background-color: green;
color: white;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 B

View File

@@ -0,0 +1,60 @@
function dropZone(opts) {
var options = opts || {},
element = opts.element || document.createElement('div'),
minHeight = opts.minHeight || '20px',
maxHeight = opts.maxHeight || '100px';
element.className = 'drop-zone';
element.style.height = minHeight;
element.textAlign = 'center';
// Event listener for when the dragged element enters the drop zone.
element.addEventListener('dragenter', function (e) {
this.classList.add('over');
this.style.height = maxHeight;
});
// Event listener for when the dragged element is over the drop zone.
element.addEventListener('dragover', function (e) {
if (e.preventDefault) {
e.preventDefault();
}
e.dataTransfer.dropEffect = 'move';
return false;
});
// Event listener for when the dragged element leaves the drop zone.
element.addEventListener('dragleave', function (e) {
this.classList.remove('over');
this.style.height = minHeight;
});
// Event listener for when the dragged element dropped in the drop zone.
element.addEventListener('drop', function (e) {
if (e.preventDefault) e.preventDefault();
if (e.stopPropagation) e.stopPropagation();
this.classList.remove('over');
this.style.height = minHeight;
if (typeof opts.onDrop === 'function') {
var dropData = e.dataTransfer.getData('text');
var payload = {};
if (dropData) {
try {
payload = JSON.parse(dropData);
} catch (err) {
payload = {};
}
}
opts.onDrop(this, payload);
}
return false;
});
return {
"element": element
};
}

View File

@@ -0,0 +1,9 @@
import {bootstrap} from 'angular2/platform/browser';
import {AppComponent} from './app.component';
import {ROUTER_PROVIDERS} from "angular2/router";
import {Authentication} from "./services/authentication";
bootstrap(AppComponent, [
ROUTER_PROVIDERS,
Authentication
]);

View File

@@ -0,0 +1,32 @@
import {Injectable} from "angular2/core";
import {Observable} from 'rxjs/Rx';
@Injectable()
export class Authentication {
token: string;
constructor() {
this.token = localStorage.getItem('token');
}
isLoggedIn() {
return !!localStorage.getItem('token');
}
login(username: String, password: String) {
if (username === 'test' && password === 'test') {
this.token = 'token';
localStorage.setItem('token', this.token);
return Observable.of('token');
}
return Observable.throw('authentication failure');
}
logout() {
this.token = undefined;
localStorage.removeItem('token');
return Observable.of(true);
}
}

View File

@@ -0,0 +1,55 @@
import {Injectable} from 'angular2/core';
// TODO: move to some namespace
declare var widgets: any;
@Injectable()
export class FormService {
getWidgetCategories() {
var result = [];
var categories = {};
var keys = Object.keys(widgets);
keys.forEach(function (key) {
var w = widgets[key];
var categoryName = w.category || 'Misc';
var category = categories[categoryName];
if (!category) {
category = {
name: categoryName,
widgets: []
};
categories[categoryName] = category;
//result.push(category);
}
category.widgets.push({
type: key,
name: w.name,
iconClass: w.iconClass
});
});
Object.keys(categories).sort().forEach(function (key) {
result.push(categories[key]);
});
return result;
}
getDragImage(widgetType: string): Element {
var w = widgets[widgetType];
// try getting exported drag image
if (w && typeof w.getDragImage === 'function') {
var img = w.getDragImage();
if (img) {
return img;
}
}
// create default drag image
var dragImage = document.createElement('button');
dragImage.className = 'btn btn-default drag-image';
dragImage.textContent = w.name;
dragImage.style.minWidth = '100px';
return dragImage;
}
}

View File

@@ -0,0 +1,16 @@
var widgets = (function (widgets) {
widgets.COMPONENT = {
create: createComponent,
name: 'COMPONENT NAME',
iconClass: 'fa fa-puzzle-piece'
};
function createComponent() {
var widget = document.createElement('button');
widget.textContent = 'Button';
widget.classList.add('btn', 'btn-default', 'widget', 'widget-button');
return widget;
}
return widgets;
})(widgets || {});

View File

@@ -0,0 +1,30 @@
var widgetUtils = (function (utils) {
utils.col = function(className) {
var col = document.createElement('div');
col.dataset.widgetId = utils.uid();
col.classList.add(className, 'widget', 'widget-col');
var dropPlaceholder = document.createElement('div');
dropPlaceholder.className = 'drop-zone';
col.appendChild(dropPlaceholder);
return col;
};
utils.getRandomColor = function () {
var color = '#'+(0x1000000+(Math.random())*0xffffff).toString(16).substr(1,6);
return color;
};
utils.uid = (function(){
var id = 0;
return function(){
if (arguments[0] === 0) {
id = 0;
}
return id++;
};
})();
return utils;
})(widgetUtils || {});

View File

@@ -0,0 +1,95 @@
var widgets = (function (widgets, utils) {
widgets.button = {
create: createComponent,
name: 'Button',
iconClass: 'fa fa-square',
category: 'Components'
};
function createComponent() {
var widget = document.createElement('button');
widget.textContent = 'Button';
widget.classList.add('btn', 'btn-default', 'widget', 'widget-button');
widget.dataset.widgetId = utils.uid();
widget.addEventListener('dblclick', function (e) {
if (this.contentEditable !== 'true') {
this.dataset.originalValue = this.textContent;
}
this.contentEditable = true;
selectElementContents(this);
});
widget.addEventListener('blur', function (e) {
if (!this.textContent) {
this.textContent = 'Button';
}
this.contentEditable = false;
});
widget.addEventListener('input', function (e) {
// when button has no text - place the stub and autoselect it
if (!this.textContent) {
this.textContent = 'Button';
selectElementContents(this);
}
});
widget.addEventListener('keydown', function (e) {
// special handling of 'spacebar' key
if (e.keyCode == 32) {
insertHtmlAtCursor('&nbsp;');
e.stopPropagation();
e.preventDefault();
return false;
}
// 'Enter' key
if (e.keyCode == 13) {
e.stopPropagation();
e.preventDefault();
this.blur();
return false;
}
// 'Esc' key
if (e.keyCode == 27) {
var originalValue = this.dataset.originalValue || 'Button';
this.textContent = originalValue;
delete this.dataset.originalValue;
this.blur();
return false;
}
});
return widget;
}
// TODO: move to shared library
function selectElementContents(el) {
var range = document.createRange();
range.selectNodeContents(el);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
// TODO: move to shared library
function insertHtmlAtCursor(html) {
var range, node;
if (window.getSelection && window.getSelection().getRangeAt) {
range = window.getSelection().getRangeAt(0);
node = range.createContextualFragment(html);
range.insertNode(node);
window.getSelection().collapseToEnd();
window.getSelection().modify('move', 'forward', 'character');
} else if (document.selection && document.selection.createRange) {
document.selection.createRange().pasteHTML(html);
document.selection.collapseToEnd();
document.selection.modify('move', 'forward', 'character');
}
}
return widgets;
})(widgets || {}, widgetUtils);

View File

@@ -0,0 +1,18 @@
var widgets = (function (widgets, utils) {
widgets.col12 = {
create: createComponent,
name: 'column 12',
iconClass: 'fa fa-puzzle-piece',
category: 'Grid system'
};
function createComponent() {
var widget = document.createElement('div');
widget.dataset.widgetId = utils.uid();
widget.classList.add('row', 'widget', 'widget-row');
widget.appendChild(utils.col('col-md-12'));
return widget;
}
return widgets;
})(widgets || {}, widgetUtils);

View File

@@ -0,0 +1,20 @@
var widgets = (function (widgets, utils) {
widgets.col2x6x4 = {
create: createComponent,
name: 'column 2:6:4',
iconClass: 'fa fa-puzzle-piece',
category: 'Grid system'
};
function createComponent() {
var widget = document.createElement('div');
widget.dataset.widgetId = utils.uid();
widget.classList.add('row', 'widget', 'widget-row');
widget.appendChild(utils.col('col-md-2'));
widget.appendChild(utils.col('col-md-6'));
widget.appendChild(utils.col('col-md-4'));
return widget;
}
return widgets;
})(widgets || {}, widgetUtils);

View File

@@ -0,0 +1,20 @@
var widgets = (function (widgets, utils) {
widgets.col4x4x4 = {
create: createComponent,
name: 'column 4:4:4',
iconClass: 'fa fa-puzzle-piece',
category: 'Grid system'
};
function createComponent() {
var widget = document.createElement('div');
widget.dataset.widgetId = utils.uid();
widget.classList.add('row', 'widget', 'widget-row');
widget.appendChild(utils.col('col-md-4'));
widget.appendChild(utils.col('col-md-4'));
widget.appendChild(utils.col('col-md-4'));
return widget;
}
return widgets;
})(widgets || {}, widgetUtils);

View File

@@ -0,0 +1,19 @@
var widgets = (function (widgets, utils) {
widgets.col6x6 = {
create: createComponent,
name: 'column 6:6',
iconClass: 'fa fa-puzzle-piece',
category: 'Grid system'
};
function createComponent() {
var widget = document.createElement('div');
widget.dataset.widgetId = utils.uid();
widget.classList.add('row', 'widget', 'widget-row');
widget.appendChild(utils.col('col-md-6'));
widget.appendChild(utils.col('col-md-6'));
return widget;
}
return widgets;
})(widgets || {}, widgetUtils);

View File

@@ -0,0 +1,20 @@
var widgets = (function (widgets, utils) {
widgets.col8x4 = {
create: createComponent,
name: 'column 8:4',
iconClass: 'fa fa-puzzle-piece',
category: 'Grid system'
};
function createComponent() {
var widget = document.createElement('div');
widget.dataset.widgetId = utils.uid();
widget.classList.add('row', 'widget', 'widget-row');
widget.appendChild(utils.col('col-md-8'));
widget.appendChild(utils.col('col-md-4'));
return widget;
}
return widgets;
})(widgets || {}, widgetUtils);

View File

@@ -0,0 +1,22 @@
var widgets = (function (widgets, utils) {
widgets.container = {
create: createComponent,
name: 'container',
iconClass: 'fa fa-puzzle-piece'
};
function createComponent() {
var widget = document.createElement('div');
widget.className = 'container widget widget-container';
widget.dataset.widgetId = utils.uid();
//widget.style.borderColor = utils.getRandomColor();
var dropPlaceholder = document.createElement('div');
dropPlaceholder.className = 'drop-zone';
widget.appendChild(dropPlaceholder);
return widget;
}
return widgets;
})(widgets || {}, widgetUtils);

View File

@@ -0,0 +1,71 @@
var widgets = (function (widgets, utils) {
widgets.input = {
create: createComponent,
name: 'Input',
iconClass: 'fa fa-file-text-o',
category: 'Components',
getDragImage: getDragImage
};
function createComponent() {
var widget = document.createElement('input');
widget.type = 'text';
widget.readOnly = true;
widget.classList.add('form-control', 'widget', 'widget-input');
widget.dataset.widgetId = utils.uid();
return widget;
}
/*function getDragImage() {
var container = document.createElement('div');
container.style.width = '200px';
var label = document.createElement('label');
label.textContent = "Input";
label.style.display = 'block';
label.style.margin = '0';
label.style.fontSize = '12px';
container.appendChild(label);
var element = document.createElement('input');
element.className = 'form-control';
element.style.display = 'block';
container.appendChild(element);
return container;
}*/
function getDragImage() {
var element = document.createElement('input');
element.style.width = '200px';
element.className = 'form-control';
return element;
}
// TODO: move to shared library
function moveCaretToEnd(el) {
if (typeof el.selectionStart == "number") {
el.selectionStart = el.selectionEnd = el.value.length;
} else if (typeof el.createTextRange != "undefined") {
el.focus();
var range = el.createTextRange();
range.collapse(false);
range.select();
}
}
// TODO: move to shared library
function setCursorPosition (element, pos) {
if (element.setSelectionRange) {
element.setSelectionRange(pos, pos);
} else if (element.createTextRange) {
var range = element.createTextRange();
range.collapse(true);
range.moveEnd('character', pos);
range.moveStart('character', pos);
range.select();
}
}
return widgets;
})(widgets || {}, widgetUtils);

View File

@@ -0,0 +1,18 @@
var widgets = (function (widgets, utils) {
widgets.row = {
create: createComponent,
name: 'row',
iconClass: 'fa fa-puzzle-piece',
category: 'Grid system'
};
function createComponent() {
var widget = document.createElement('div');
widget.classList.add('row', 'widget', 'widget-row');
widget.dataset.widgetId = utils.uid();
widget.appendChild(utils.col('col-md-12'));
return widget;
}
return widgets;
})(widgets || {}, widgetUtils);

61
demo-shell-ng2/index.html Normal file
View File

@@ -0,0 +1,61 @@
<html>
<head>
<base href="/">
<title>Angular 2 QuickStart</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.min.css">
<link href="node_modules/font-awesome/css/font-awesome.min.css" rel="stylesheet">
<link href="app/css/menu.css" rel="stylesheet">
<link href="app/css/widgets.css" rel="stylesheet">
<link href="app/css/app.css" rel="stylesheet">
<link href="app/css/designer.css" rel="stylesheet">
<!-- Custom theme -->
<link rel="stylesheet" href="app/css/theme/navbar.css">
<!-- 1. Load libraries -->
<!-- IE required polyfills, in this exact order -->
<script src="node_modules/es6-shim/es6-shim.min.js"></script>
<script src="node_modules/systemjs/dist/system-polyfills.js"></script>
<script src="node_modules/angular2/es6/dev/src/testing/shims_for_IE.js"></script>
<script src="node_modules/angular2/bundles/angular2-polyfills.js"></script>
<script src="node_modules/systemjs/dist/system.src.js"></script>
<script src="node_modules/rxjs/bundles/Rx.js"></script>
<script src="node_modules/angular2/bundles/angular2.dev.js"></script>
<script src="node_modules/angular2/bundles/router.dev.js"></script>
<script src="node_modules/jquery/dist/jquery.min.js"></script>
<script src="node_modules/bootstrap/dist/js/bootstrap.min.js"></script>
<script src="app/js/dropZone.js"></script>
<script src="app/widgets/widgetUtils.js"></script>
<script src="app/widgets/widgets-container.js"></script>
<script src="app/widgets/widgets-button.js"></script>
<script src="app/widgets/widgets-input.js"></script>
<script src="app/widgets/widgets-row.js"></script>
<script src="app/widgets/widgets-col-12.js"></script>
<script src="app/widgets/widgets-col-6x6.js"></script>
<script src="app/widgets/widgets-col-8x4.js"></script>
<script src="app/widgets/widgets-col-4x4x4.js"></script>
<script src="app/widgets/widgets-col-2x6x4.js"></script>
<!-- 2. Configure SystemJS -->
<script>
System.config({
packages: {
app: {
format: 'register',
defaultExtension: 'js'
}
}
});
System.import('app/main')
.then(null, console.error.bind(console));
</script>
</head>
<!-- 3. Display the application -->
<body class="cbp-spmenu-push">
<my-app>Loading...</my-app>
</body>
</html>

View File

@@ -0,0 +1,30 @@
{
"name": "angular2-quickstart",
"version": "1.0.0",
"scripts": {
"start": "concurrently \"npm run tsc:w\" \"npm run lite\" ",
"tsc": "tsc",
"tsc:w": "tsc -w",
"lite": "lite-server",
"typings": "typings",
"postinstall": "typings install"
},
"license": "ISC",
"dependencies": {
"angular2": "2.0.0-beta.13",
"bootstrap": "^3.3.6",
"es6-shim": "^0.35.0",
"font-awesome": "^4.5.0",
"jquery": "^2.2.2",
"reflect-metadata": "0.1.2",
"rxjs": "5.0.0-beta.2",
"systemjs": "0.19.25",
"zone.js": "0.6.6"
},
"devDependencies": {
"concurrently": "^2.0.0",
"lite-server": "^2.1.0",
"typescript": "^1.8.9",
"typings": "^0.7.11"
}
}

View File

@@ -0,0 +1,17 @@
{
"compilerOptions": {
"target": "es5",
"module": "system",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": false,
"noImplicitAny": false
},
"exclude": [
"node_modules",
"typings/main",
"typings/main.d.ts"
]
}

View File

@@ -0,0 +1,7 @@
{
"ambientDependencies": {
"es6-shim": "github:DefinitelyTyped/DefinitelyTyped/es6-shim/es6-shim.d.ts#4de74cb527395c13ba20b438c3a7a419ad931f1c",
"jasmine": "github:DefinitelyTyped/DefinitelyTyped/jasmine/jasmine.d.ts#d594ef506d1efe2fea15f8f39099d19b39436b71",
"jquery": "registry:dt/jquery#1.10.0+20160316155526"
}
}