added global navigation library and implemented it in the marketplace

pull/18/head
Chris Veilleux 2018-11-08 20:50:54 -06:00
parent f2718378e5
commit 9fba5f6371
41 changed files with 1735 additions and 274 deletions

View File

@ -433,6 +433,41 @@
}
}
}
},
"globalnav": {
"root": "projects/globalnav",
"sourceRoot": "projects/globalnav/src",
"projectType": "library",
"prefix": "globalnav",
"architect": {
"build": {
"builder": "@angular-devkit/build-ng-packagr:build",
"options": {
"tsConfig": "projects/globalnav/tsconfig.lib.json",
"project": "projects/globalnav/ng-package.json"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "projects/globalnav/src/test.ts",
"tsConfig": "projects/globalnav/tsconfig.spec.json",
"karmaConfig": "projects/globalnav/karma.conf.js"
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"projects/globalnav/tsconfig.lib.json",
"projects/globalnav/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
}
},
"defaultProject": "internet"

File diff suppressed because it is too large Load Diff

View File

@ -35,6 +35,7 @@
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.10.0",
"@angular-devkit/build-ng-packagr": "~0.10.0",
"@angular/cli": "~7.0.3",
"@angular/compiler-cli": "~7.0.0",
"@angular/language-service": "~7.0.0",
@ -49,8 +50,11 @@
"karma-coverage-istanbul-reporter": "~2.0.1",
"karma-jasmine": "~1.1.2",
"karma-jasmine-html-reporter": "^0.2.2",
"ng-packagr": "^4.2.0",
"protractor": "~5.4.0",
"ts-node": "~7.0.0",
"tsickle": ">=0.29.0",
"tslib": "^1.9.0",
"tslint": "~5.11.0",
"typescript": "~3.1.1"
}

View File

@ -0,0 +1,31 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, '../../coverage'),
reports: ['html', 'lcovonly'],
fixWebpackSourcePaths: true
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false
});
};

View File

@ -0,0 +1,13 @@
{
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../dist/globalnav",
"lib": {
"entryFile": "src/public_api.ts",
"cssUrl": "inline",
"umdModuleIds": {
"@fortawesome/angular-fontawesome": "angularFontawesome",
"@fortawesome/free-brands-svg-icons": "freeBrandsSvgIcons",
"@fortawesome/free-solid-svg-icons": "freeSolidSvgIcons"
}
}
}

View File

@ -0,0 +1,10 @@
{
"name": "globalnav",
"version": "0.0.1",
"peerDependencies": {
"@angular/common": "^7.0.0",
"@angular/core": "^7.0.0",
"@angular/flex-layout": "^7.0.0-beta.19"
}
}

View File

@ -0,0 +1,25 @@
<div fxFlex="nogrow" class="nav-footer">
<mat-divider></mat-divider>
<div fxLayout="row wrap" fxLayoutAlign="start" style="margin-left: -10px;">
<button
*ngFor="let media of socialMediaIcons"
mat-icon-button
(click)="navigateToSocialMedia(media.url)"
>
<fa-icon [icon]="media.icon"></fa-icon>
</button>
</div>
<div>
<button mat-flat-button (click)="navigateToContactUs()" class="footer-text">Contact Us</button>
</div>
<div>
<button mat-flat-button (click)="navigateToMediaKit()" class="footer-text">Media Kit</button>
</div>
<div>
<button mat-flat-button (click)="navigateToTermsOfUse()" class="footer-text">Terms of Use</button>
</div>
<div>
<button mat-flat-button (click)="navigateToPrivacyPolicy()" class="footer-text">Privacy Policy</button>
</div>
<div class="mat-body-2">&copy; Mycroft AI, Inc.</div>
</div>

View File

@ -0,0 +1,24 @@
.nav-footer {
//bottom: 0;
padding: 16px;
//position: fixed;
}
fa-icon {
color: #6c7a89;
font-size: 20px;
}
.footer-text {
color: #6c7a89;
line-height: 30px;
padding: 0;
}
.mat-body-2 {
color: #6c7a89;
margin-top: 5px;
}
.mat-divider.mat-divider-vertical {
height: 30px;
}

View File

@ -0,0 +1,59 @@
import { Component, Input, OnInit } from '@angular/core';
import {
faFacebook,
faInstagram,
faLinkedin,
faMedium,
faReddit,
faTelegram,
faTwitter,
faYoutube,
} from '@fortawesome/free-brands-svg-icons';
@Component({
selector: 'globalnav-footer',
templateUrl: './footer.component.html',
styleUrls: ['./footer.component.scss']
})
export class FooterComponent implements OnInit {
@Input() contactUsUrl: string;
@Input() mediaKitUrl: string;
@Input() privacyPolicyUrl: string;
public socialMediaIcons = [
{icon: faFacebook, url: 'https://www.facebook.com/aiforeveryone/'},
{icon: faInstagram, url: 'https://www.instagram.com/mycroft_ai/'},
{icon: faLinkedin, url: 'https://www.linkedin.com/company/mycroft-a.i./'},
{icon: faMedium, url: 'https://medium.com/@mycroftai'},
{icon: faReddit, url: 'https://www.reddit.com/r/Mycroftai/'},
{icon: faTelegram, url: 'https://t.me/mycroft_ai'},
{icon: faTwitter, url: 'https://twitter.com/mycroft_ai'},
{icon: faYoutube, url: 'https://www.youtube.com/channel/UC1dlmB1lup9RwFQBSGnhA-g'}
];
@Input() termsOfUseUrl: string;
constructor() { }
ngOnInit() {
}
navigateToSocialMedia(url) {
window.location.assign(url);
}
navigateToTermsOfUse() {
window.location.assign(this.termsOfUseUrl);
}
navigateToPrivacyPolicy() {
window.location.assign(this.privacyPolicyUrl);
}
navigateToContactUs() {
window.location.assign(this.contactUsUrl);
}
navigateToMediaKit() {
window.location.assign(this.mediaKitUrl);
}
}

View File

@ -0,0 +1,29 @@
<mat-sidenav-container>
<mat-sidenav [mode]="'side'" [disableClose]="true" [opened]="true" [fixedInViewport]="true">
<div fxFill fxLayout="column">
<div fxFlex="initial">
<div class="globalnav-logo"></div>
<mat-divider></mat-divider>
</div>
<div fxFlex>
<mat-nav-list >
<globalnav-primary-nav-item
*ngFor="let nav of navigationItems"
[primaryNavItem]="nav"
>
</globalnav-primary-nav-item>
</mat-nav-list>
</div>
<globalnav-footer
[contactUsUrl]="contactUsUrl"
[mediaKitUrl]="mediaKitUrl"
[privacyPolicyUrl]="privacyPolicyUrl"
[termsOfUseUrl]="termsOfUseUrl"
>
</globalnav-footer>
</div>
</mat-sidenav>
<mat-sidenav-content>
<ng-content select="[appBody]"></ng-content>
</mat-sidenav-content>
</mat-sidenav-container>

View File

@ -0,0 +1,23 @@
// Use inline css to display the logo at top of sidebar because Angular
// libraries do not support static assets yet.
.globalnav-logo {
background: url("sidenav-logo.svg") no-repeat;
height: 30px;
margin: 20px;
}
mat-sidenav {
width: 220px;
mat-divider {
width: 200px;
margin-left: 10px;
}
mat-nav-list {
overflow-y: auto;
}
}
mat-sidenav-content {
background-color: #f1f3f4;
}

View File

@ -0,0 +1,130 @@
import { Component, Input, OnInit } from '@angular/core';
import { PrimaryNavItem } from './globalnav.service';
import {
faLightbulb,
faRobot,
faRocket,
faRss,
faStore,
faUser,
faUsers
} from '@fortawesome/free-solid-svg-icons';
@Component({
selector: 'globalnav-sidenav',
templateUrl: './globalnav.component.html',
styleUrls: ['./globalnav.component.scss']
})
export class GlobalnavComponent implements OnInit {
public navigationItems: PrimaryNavItem[];
@Input() environment: any;
public contactUsUrl: string;
public mediaKitUrl: string;
public termsOfUseUrl: string;
public privacyPolicyUrl: string;
public isLoggedIn: boolean;
constructor() {
}
ngOnInit() {
this.buildNavigationItems();
this.setLoginStatus();
this.buildAccountNav();
}
buildNavigationItems(): void {
const aboutMycroftNav: PrimaryNavItem = {
children: [
{text: 'Team', url: this.environment.wordpressUrl + '/team'},
{text: 'Careers', url: this.environment.wordpressUrl + '/careers'}
],
icon: faRobot,
text: 'About Mycroft'
};
const blogNav: PrimaryNavItem = {
icon: faRss,
text: 'Blog',
url: this.environment.wordpressUrl + '/blog'
};
const communityNav: PrimaryNavItem = {
children: [
{text: 'Chat', url: this.environment.chatUrl},
{text: 'Forum', url: this.environment.forumUrl}
],
icon: faUsers,
text: 'Community'
};
const contributeNav: PrimaryNavItem = {
children: [
{text: 'GitHub', url: 'https://github.com/MycroftAI'},
{text: 'Translate', url: this.environment.translateUrl},
{text: 'Wake Word Tagger', url: this.environment.accountUrl + '/#/precise'},
{text: 'Text-to-Speech Tagger', url: this.environment.accountUrl + '/#/deepspeech'}
],
icon: faLightbulb,
text: 'Contribute'
};
const getStartedNav: PrimaryNavItem = {
children: [
{text: 'Get Mycroft', url: this.environment.wordpressUrl + '/download'},
{text: 'Documentation', url: this.environment.wordpressUrl + '/documentation'}
],
icon: faRocket,
text: 'Get Started'
};
const marketplaceNav: PrimaryNavItem = {
children: [
{text: 'Skills', url: this.environment.marketplaceUrl + '/skills'},
{text: 'Hardware', url: this.environment.wordpressUrl + '/shop'}
],
icon: faStore,
text: 'Marketplace'
};
this.navigationItems = [
aboutMycroftNav,
getStartedNav,
blogNav,
communityNav,
contributeNav,
marketplaceNav,
];
this.contactUsUrl = this.environment.wordpressUrl + '/contact';
this.mediaKitUrl = this.environment.wordpressUrl + '/media';
this.privacyPolicyUrl = this.environment.accountUrl + '/#/privacy-policy';
this.termsOfUseUrl = this.environment.accountUrl + '/#/terms-of-use';
}
setLoginStatus(): void {
const cookies = document.cookie;
const seleneTokenExists = cookies.includes('seleneToken');
const seleneTokenEmpty = cookies.includes('seleneToken=""');
this.isLoggedIn = seleneTokenExists && !seleneTokenEmpty;
}
buildAccountNav() {
const accountNav: PrimaryNavItem = {
icon: faUser,
text: 'Account'
};
if (this.isLoggedIn) {
accountNav.children = [
{text: 'Devices', url: this.environment.accountUrl + '/#/device'},
{text: 'Profile', url: this.environment.accountUrl + '/#/profile'},
{text: 'Skill Settings', url: this.environment.accountUrl + '/#/skill'},
{text: 'Subscription', url: this.environment.accountUrl + '/#/account'},
{text: 'User Settings', url: this.environment.accountUrl + '/#/setting/basic'},
{text: 'Logout', url: this.environment.singleSignOnUrl + '/logout?redirect=' + window.location.href}
];
} else {
accountNav.children = [
{text: 'Log In', url: this.environment.singleSignOnUrl + '/login?redirect=' + window.location.href},
{text: 'Sign Up', url: this.environment.singleSignOnUrl + '/signup'}
];
}
this.navigationItems.push(accountNav);
}
}

View File

@ -0,0 +1,42 @@
import { CommonModule } from '@angular/common';
import { FlexLayoutModule } from '@angular/flex-layout';
import { NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatDividerModule } from '@angular/material/divider';
import { MatExpansionModule } from '@angular/material/expansion';
import { MatListModule } from '@angular/material';
import { MatSidenavModule } from '@angular/material/sidenav';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { GlobalnavComponent } from './globalnav.component';
import { GlobalnavService } from './globalnav.service';
import { NavItemComponent } from './nav-item/nav-item.component';
import { PrimaryNavItemComponent } from './primary-nav-item/primary-nav-item.component';
import { FooterComponent } from './footer/footer.component';
@NgModule({
imports: [
CommonModule,
FlexLayoutModule,
FontAwesomeModule,
MatButtonModule,
MatDividerModule,
MatExpansionModule,
MatListModule,
MatSidenavModule
],
declarations: [
GlobalnavComponent,
NavItemComponent,
PrimaryNavItemComponent,
FooterComponent
],
exports: [
GlobalnavComponent
],
providers: [
GlobalnavService
]
})
export class GlobalnavModule { }

View File

@ -0,0 +1,25 @@
import { Injectable } from '@angular/core';
import { IconDefinition } from '@fortawesome/free-solid-svg-icons';
export interface NavItem {
text: string;
url: string;
}
export interface PrimaryNavItem {
children?: NavItem[];
icon: IconDefinition;
text: string;
url?: string;
}
@Injectable({
providedIn: 'root'
})
export class GlobalnavService {
constructor() {
}
}

View File

@ -0,0 +1,6 @@
<mat-list-item (click)="navigateToUrl()">
<div fxLayout="row" [ngStyle]="navItemStyle">
<span fxFlex="20"></span>
<span fxFlex class="mat-body-1">{{item.text}}</span>
</div>
</mat-list-item>

View File

@ -0,0 +1,4 @@
.mat-list-item {
height: 30px;
}

View File

@ -0,0 +1,33 @@
import { Component, Input, OnInit } from '@angular/core';
import { NavItem } from '../globalnav.service';
@Component({
selector: 'globalnav-nav-item',
templateUrl: './nav-item.component.html',
styleUrls: ['./nav-item.component.scss']
})
export class NavItemComponent implements OnInit {
@Input() item: NavItem;
public navItemStyle: object;
constructor() {
}
ngOnInit() {
this.buildNavItemStyle();
}
buildNavItemStyle() {
this.navItemStyle = {'width': '100%'};
if (window.location.href.includes(this.item.url)) {
this.navItemStyle['background-color'] = '#22a7f0';
this.navItemStyle['color'] = 'white';
this.navItemStyle['border-radius'] = '4px';
}
}
navigateToUrl() {
window.location.assign(this.item.url);
}
}

View File

@ -0,0 +1,15 @@
<mat-list-item (click)="onItemSelected()" class="nav-item">
<div fxLayout="row" style="width: 100%;">
<fa-icon fxFlex="20" [icon]="primaryNavItem.icon" class="nav-item-icon"></fa-icon>
<span fxFlex>
{{primaryNavItem.text}}
</span>
</div>
</mat-list-item>
<div *ngIf="expanded">
<globalnav-nav-item
*ngFor="let child of primaryNavItem.children"
[item]="child"
>
</globalnav-nav-item>
</div>

View File

@ -0,0 +1,7 @@
.nav-item:hover {
background-color: #e4f1fe;
}
.nav-item-icon {
color: #22a7f0;
}

View File

@ -0,0 +1,39 @@
import { Component, Input, OnInit } from '@angular/core';
import { PrimaryNavItem } from '../globalnav.service';
@Component({
selector: 'globalnav-primary-nav-item',
templateUrl: './primary-nav-item.component.html',
styleUrls: ['./primary-nav-item.component.scss']
})
export class PrimaryNavItemComponent implements OnInit {
public expanded = false;
@Input() primaryNavItem: PrimaryNavItem;
constructor() { }
ngOnInit() {
this.expandCurrentLocation();
}
expandCurrentLocation() {
if (this.primaryNavItem.children && this.primaryNavItem.children.length) {
this.primaryNavItem.children.forEach(
(navItem) => {
if (window.location.href.includes(navItem.url)) {
this.expanded = true;
}
}
);
}
}
onItemSelected() {
if (this.primaryNavItem.children && this.primaryNavItem.children.length) {
this.expanded = !this.expanded;
} else {
window.location.assign(this.primaryNavItem.url);
}
}
}

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1181 212" style="enable-background:new 0 0 1181 212;" xml:space="preserve">
<style type="text/css">
.st0{fill:#22A7F0;}
.st1{fill:#FFFFFF;}
.st2{fill:#2C3E50;}
</style>
<g>
<path class="st2" d="M50.5,81l41.3,91.7L133.2,81c1.5-3.3,4.8-5.5,8.5-5.5h0.2c5.1,0,9.3,4.2,9.3,9.3v98.3c0,3.2-2.6,5.8-5.8,5.8h0
c-3.2,0-5.8-2.6-5.8-5.8v-40.6l1.1-52.8L98,184.9c-1.1,2.4-3.5,4-6.2,4h0c-2.7,0-5.1-1.6-6.2-4L43.3,90.3l1.1,52.2v40.6
c0,3.2-2.6,5.8-5.8,5.8h0c-3.2,0-5.8-2.6-5.8-5.8V84.8c0-5.1,4.2-9.3,9.3-9.3h0C45.7,75.6,49,77.7,50.5,81z"/>
<path class="st2" d="M232.5,137.4L267,78.4c1-1.7,2.9-2.8,4.9-2.8h0c4.4,0,7.1,4.8,4.9,8.6l-38.5,63.6v35.3c0,3.2-2.6,5.8-5.8,5.8
h0c-3.2,0-5.8-2.6-5.8-5.8v-35.8l-38.2-63.1c-2.3-3.8,0.4-8.6,4.9-8.6h0c2,0,3.9,1.1,4.9,2.8L232.5,137.4z"/>
<path class="st2" d="M394.3,153.2c3.7,0,6.5,3.4,5.6,7c-2.1,8.6-6.4,15.5-12.6,20.6c-7.9,6.4-18.3,9.6-31.4,9.6
c-13.8,0-24.9-4.6-33.4-13.9c-8.5-9.3-12.8-22.1-12.8-38.3v-12.5c0-10.2,1.9-19.3,5.8-27.2c3.9-7.9,9.3-13.9,16.4-18.2
c7.1-4.2,15.3-6.4,24.6-6.4c13,0,23.3,3.3,31,9.8c6.1,5.2,10.3,12.1,12.4,20.8c0.9,3.6-2,7-5.6,7h0c-2.7,0-5-1.8-5.6-4.4
c-1.6-6.7-4.5-12-8.8-15.9c-5.3-4.8-13-7.1-23.3-7.1c-10.9,0-19.5,3.7-25.8,11.1c-6.3,7.4-9.4,17.7-9.4,30.9v12.8
c0,12.9,3.1,23.1,9.3,30.5c6.2,7.5,14.6,11.2,25.3,11.2c10.4,0,18.3-2.3,23.7-6.8c4.5-3.8,7.5-9.2,9.1-16.2
C389.3,155,391.6,153.2,394.3,153.2L394.3,153.2z"/>
<path class="st2" d="M486.2,143.4h-34.4v39.7c0,3.2-2.6,5.8-5.8,5.8h0c-3.2,0-5.8-2.6-5.8-5.8V75.6h40.2c13,0,23.2,3,30.6,9.1
c7.4,6.1,11,14.5,11,25.1c0,7.5-2.3,14-6.8,19.5c-4.5,5.5-10.5,9.4-18,11.4l25.1,39.5c2.2,3.5-0.1,8.1-4.2,8.4l0,0
c-2,0.2-4-0.8-5.1-2.6L486.2,143.4z M451.7,133.4h30.7c8.4,0,15.2-2.2,20.2-6.5c5.1-4.3,7.6-9.9,7.6-16.8c0-7.6-2.6-13.5-7.8-17.9
c-5.2-4.3-12.5-6.5-21.8-6.6h-28.9V133.4z"/>
<path class="st2" d="M789.9,137h-51.2v46.1c0,3.2-2.6,5.8-5.8,5.8l0,0c-3.2,0-5.8-2.6-5.8-5.8V75.6h70.9c2.7,0,5,2.2,5,5v0.2
c0,2.7-2.2,5-5,5h-59.3v41.4h51.2c2.7,0,5,2.2,5,5v0C794.9,134.8,792.7,137,789.9,137z"/>
<path class="st2" d="M916.8,85.7h-36.4v97.4c0,3.2-2.6,5.8-5.8,5.8l0,0c-3.2,0-5.8-2.6-5.8-5.8V85.7h-36.3c-2.8,0-5.1-2.3-5.1-5.1
v0c0-2.8,2.3-5.1,5.1-5.1h84.3c2.8,0,5.1,2.3,5.1,5.1v0C921.8,83.4,919.6,85.7,916.8,85.7z"/>
<path class="st2" d="M1085.8,158.6h-55l-10.7,26.8c-0.8,2.1-2.9,3.5-5.1,3.5h-0.1c-3.9,0-6.6-4-5.1-7.6l40.9-100.6
c1.3-3.1,4.3-5.2,7.7-5.2l0,0c3.4,0,6.4,2.1,7.7,5.2l40.6,100.5c1.5,3.6-1.2,7.6-5.1,7.6l0,0c-2.3,0-4.3-1.4-5.1-3.5L1085.8,158.6z
M1034.7,148.7h47.1l-23.4-59.3L1034.7,148.7z"/>
<path class="st2" d="M1150.2,188.9L1150.2,188.9c-3.2,0-5.8-2.6-5.8-5.8V81.4c0-3.2,2.6-5.8,5.8-5.8l0,0c3.2,0,5.8,2.6,5.8,5.8
v101.7C1156,186.3,1153.4,188.9,1150.2,188.9z"/>
<g>
<path class="st0" d="M623.4,66.2c-36.1,0-65.3,29.2-65.3,65.3s29.2,65.3,65.3,65.3s65.3-29.2,65.3-65.3S659.4,66.2,623.4,66.2z
M623.4,186.7c-30.5,0-55.2-24.8-55.2-55.2c0-30.5,24.8-55.2,55.2-55.2s55.2,24.8,55.2,55.2C678.6,162,653.8,186.7,623.4,186.7z"
/>
<path class="st0" d="M577.9,131.5c0,25.1,20.3,45.4,45.4,45.4V86.1C598.3,86.1,577.9,106.4,577.9,131.5z"/>
<circle class="st0" cx="623.4" cy="37.8" r="11.6"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -0,0 +1,7 @@
/*
* Public API Surface of globalnav
*/
export * from './lib/globalnav.service';
export * from './lib/globalnav.component';
export * from './lib/globalnav.module';

View File

@ -0,0 +1,22 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'core-js/es7/reflect';
import 'zone.js/dist/zone';
import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
declare const require: any;
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);

View File

@ -0,0 +1,32 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "../../out-tsc/lib",
"target": "es2015",
"module": "es2015",
"moduleResolution": "node",
"declaration": true,
"sourceMap": true,
"inlineSources": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"importHelpers": true,
"types": [],
"lib": [
"dom",
"es2018"
]
},
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"skipTemplateCodegen": true,
"strictMetadataEmit": true,
"fullTemplateTypeCheck": true,
"strictInjectionParameters": true,
"enableResourceInlining": true
},
"exclude": [
"src/test.ts",
"**/*.spec.ts"
]
}

View File

@ -0,0 +1,17 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "../../out-tsc/spec",
"types": [
"jasmine",
"node"
]
},
"files": [
"src/test.ts"
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}

View File

@ -0,0 +1,17 @@
{
"extends": "../../tslint.json",
"rules": {
"directive-selector": [
true,
"attribute",
"globalnav",
"camelCase"
],
"component-selector": [
true,
"element",
"globalnav",
"kebab-case"
]
}
}

View File

@ -1,4 +1,9 @@
<market-header></market-header>
<div class="app-body">
<router-outlet></router-outlet>
</div>
<globalnav-sidenav [environment]="environment">
<!--
Inject the marketplace app into the <mat-sidenav-content> component
of the global navigation menu library.
-->
<div appBody class="app-body">
<router-outlet></router-outlet>
</div>
</globalnav-sidenav>

View File

@ -5,3 +5,8 @@
margin-right: 3vw;
margin-top: 30px;
}
img {
height: 20px;
margin-top: -7px;
}

View File

@ -1,11 +1,18 @@
import { Component, OnInit } from '@angular/core';
import { environment } from '../environments/environment';
@Component({
selector: 'market-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
constructor() { }
ngOnInit() { }
public environment = environment;
constructor() {
}
ngOnInit() {
}
}

View File

@ -5,9 +5,8 @@ import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HeaderModule } from './header/header.module';
import { GlobalnavModule } from 'globalnav';
import { MaterialModule } from './shared/material.module';
import { LoginService } from './shared/login.service';
import { SkillsModule } from './skills/skills.module';
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
@ -17,13 +16,13 @@ import { PageNotFoundComponent } from './page-not-found/page-not-found.component
imports: [
BrowserModule,
BrowserAnimationsModule,
GlobalnavModule,
HttpClientModule,
HeaderModule,
MaterialModule,
SkillsModule,
AppRoutingModule
],
providers: [ LoginService ],
providers: [ ],
bootstrap: [ AppComponent ]
}
)

View File

@ -1,22 +0,0 @@
<mat-toolbar>
<img src="../../assets/header-logo.svg">
<fa-icon class='separator' [icon]="separatorIcon"></fa-icon>
<div class="mat-subheading-1" style="margin-bottom: 0">MARKETPLACE</div>
<div fxFlex fxLayout="row" fxLayoutAlign="end center">
<button mat-button (click)="login()" *ngIf="!isLoggedIn">
<fa-icon [icon]="signInIcon"></fa-icon>
LOG IN
</button>
<button mat-button class="menu-button" [matMenuTriggerFor]="menu" *ngIf="isLoggedIn">
{{userMenuButtonText}}
<fa-icon [icon]="menuButtonIcon"></fa-icon>
</button>
<mat-menu [overlapTrigger]="false" #menu="matMenu">
<button mat-menu-item (click)="logout()">
<fa-icon [icon]="signOutIcon"></fa-icon>
Logout
</button>
</mat-menu>
</div>
</mat-toolbar>

View File

@ -1,26 +0,0 @@
@import '../../stylesheets/global';
mat-toolbar {
background-color: $mycroft-primary;
color: $mycroft-white;
img {
height: 20px;
margin-top: -7px;
}
.separator {
font-size: 5px;
padding-left: 10px;
padding-right: 10px;
}
.mat-subheading-1 {
margin-bottom: 0;
}
fa-icon {
padding-right: 5px;
}
.menu-button {
fa-icon {
padding-left: 5px;
}
}
}

View File

@ -1,60 +0,0 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs/internal/Subscription';
import {
faCaretDown,
faCircle,
faSignInAlt,
faSignOutAlt
} from '@fortawesome/free-solid-svg-icons';
import { InstallService } from '../skills/install.service';
import { LoginService } from '../shared/login.service';
@Component({
selector: 'market-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.scss']
})
export class HeaderComponent implements OnInit, OnDestroy {
public isLoggedIn: boolean;
private loginStatus: Subscription;
public separatorIcon = faCircle;
public signInIcon = faSignInAlt;
public signOutIcon = faSignOutAlt;
public menuButtonIcon = faCaretDown;
public userMenuButtonText: string;
constructor(
private installService: InstallService,
private loginService: LoginService
) { }
ngOnInit() {
this.loginStatus = this.loginService.isLoggedIn.subscribe(
(isLoggedIn) => { this.onLoginStateChange(isLoggedIn); }
);
this.loginService.setLoginStatus();
}
ngOnDestroy() {
this.loginStatus.unsubscribe();
}
onLoginStateChange(isLoggedIn) {
this.isLoggedIn = isLoggedIn;
if (isLoggedIn) {
this.loginService.getUser().subscribe(
(user) => { this.userMenuButtonText = user.name; }
);
}
}
login() {
this.loginService.login();
}
logout() {
this.loginService.logout();
}
}

View File

@ -1,22 +0,0 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FlexLayoutModule } from '@angular/flex-layout';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { InstallService } from '../skills/install.service';
import { HeaderComponent } from './header.component';
import { MaterialModule } from '../shared/material.module';
@NgModule({
imports: [
CommonModule,
FlexLayoutModule,
FontAwesomeModule,
MaterialModule
],
declarations: [ HeaderComponent],
exports: [ HeaderComponent ],
providers: [ InstallService ]
})
export class HeaderModule { }

View File

@ -1,15 +0,0 @@
import { TestBed, inject } from '@angular/core/testing';
import { LoginService } from './login.service';
describe('LoginService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [LoginService]
});
});
it('should be created', inject([LoginService], (service: LoginService) => {
expect(service).toBeTruthy();
}));
});

View File

@ -1,41 +0,0 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/internal/Observable';
import { Subject } from 'rxjs/internal/Subject';
import { environment } from '../../environments/environment';
const redirectQuery = '?redirect=';
export class User {
name: string;
}
@Injectable()
export class LoginService {
public isLoggedIn = new Subject<boolean>();
public loginUrl: string = environment.loginUrl + '/login';
private logoutUrl = environment.loginUrl + '/logout';
private userUrl = '/api/user';
constructor(private http: HttpClient) {
}
getUser(): Observable<User> {
return this.http.get<User>(this.userUrl);
}
setLoginStatus(): void {
const cookies = document.cookie;
const seleneTokenExists = cookies.includes('seleneToken');
const seleneTokenEmpty = cookies.includes('seleneToken=""');
this.isLoggedIn.next( seleneTokenExists && !seleneTokenEmpty);
}
login(): void {
window.location.assign(this.loginUrl + redirectQuery + window.location.href);
}
logout(): void {
window.location.assign(this.logoutUrl + redirectQuery + window.location.href);
}
}

View File

@ -1,4 +1,10 @@
export const environment = {
production: false,
loginUrl: 'http://login.mycroft.test'
chatUrl: 'https://chat.mycroft.ai',
forumUrl: 'https://forum.mycroft.ai',
singleSignOnUrl: 'http://sso.mycroft.test',
accountUrl: 'https://home-test.mycroft.ai',
marketplaceUrl: 'http://market.mycroft.test',
translateUrl: 'https://translate-test.mycroft.ai',
wordpressUrl: 'https://test.mycroft.ai'
};

View File

@ -1,6 +1,11 @@
export const environment = {
production: true,
loginUrl: 'https://login.mycroft.ai'
chatUrl: 'https://chat.mycroft.ai',
forumUrl: 'https://forum.mycroft.ai',
singleSignOnUrl: 'https://sso.mycroft.ai',
accountUrl: 'https://home.mycroft.ai',
marketplaceUrl: 'https://market.mycroft.ai',
translateUrl: 'https://translate.mycroft.ai',
wordpressUrl: 'https://test.mycroft.ai'
};
document.write(

View File

@ -1,4 +1,10 @@
export const environment = {
production: false,
loginUrl: 'https://login.mycroft-test.net'
chatUrl: 'https://chat.mycroft.ai',
forumUrl: 'https://forum.mycroft.ai',
singleSignOnUrl: 'https://sso.mycroft-test.net',
accountUrl: 'https://home-test.mycroft.ai',
marketplaceUrl: 'https://market.mycroft-test.net',
translateUrl: 'https://translate-test.mycroft.ai',
wordpressUrl: 'https://test.mycroft.ai'
};

View File

@ -4,8 +4,13 @@
export const environment = {
production: false,
apiUrl: 'http://localhost:5002',
loginUrl: 'http://localhost:4201'
chatUrl: 'https://chat.mycroft.ai',
forumUrl: 'https://forum.mycroft.ai',
singleSignOnUrl: 'http://localhost:4201',
accountUrl: 'https://home-test.mycroft.ai',
marketplaceUrl: 'http://localhost:4202',
translateUrl: 'https://translate-test.mycroft.ai',
wordpressUrl: 'https://test.mycroft.ai'
};
/*

View File

@ -16,6 +16,14 @@
"lib": [
"es2018",
"dom"
]
],
"paths": {
"globalnav": [
"dist/globalnav"
],
"globalnav/*": [
"dist/globalnav/*"
]
}
}
}
}