This commit is contained in:
peter
2019-08-03 13:50:46 +02:00
parent 4edf6c1a7f
commit d9ccd4d9d3
76 changed files with 22035 additions and 1902 deletions
+5 -12
View File
@@ -41,32 +41,25 @@ steps:
repo: registry.frontblock.me/vendor/admin repo: registry.frontblock.me/vendor/admin
tags: ${DRONE_BUILD_CREATED} tags: ${DRONE_BUILD_CREATED}
- name: archive static files - name: archive plugin files
image: alpine:3.9 image: alpine:3.9
commands: commands:
- apk add --no-cache zip tar - apk add --no-cache zip
- mkdir dist - zip -9r ./dist/plugin.zip static
- zip -9r ./dist/${DRONE_REPO_NAME}.zip static
- tar -czf ./dist/${DRONE_REPO_NAME}.tar.gz static
when: when:
event: event:
- tag - tag
- name: release static files - name: release plugin archive
image: plugins/gitea-release image: plugins/gitea-release
settings: settings:
base_url: https://gitea.frontblock.me base_url: https://gitea.frontblock.me
api_key: api_key:
from_secret: release_token from_secret: release_token
files: files:
- dist/* - plugin.zip
checksum: checksum:
- md5 - md5
- sha1
- sha256
- sha512
- adler32
- crc32
when: when:
event: event:
- tag - tag
+1060 -638
View File
File diff suppressed because it is too large Load Diff
+5 -3
View File
@@ -3,9 +3,11 @@
"version": "1.0.0", "version": "1.0.0",
"scripts": { "scripts": {
"tsc": "tsc", "tsc": "tsc",
"start": "npm run build; node lib/backendsrc/FrontblockAdmin.js", "start": "npm run build; node lib/FrontblockAdmin.js",
"build": "tsc; webpack --display-error-details --config static.webpack.config.prod.js --progress --colors", "build": "npm run clean; npm run build-backend; npm run build-frontend",
"clean": "rm -rf lib", "build-backend": "tsc; webpack --display-error-details --config static.webpack.config.prod.js --progress --colors",
"build-frontend": "cd src/frontend; npm run build; cp -r dist/* ../../static",
"clean": "rm -rf lib static; rm -rf src/frontend/dist",
"update-frontblock": "rm -rf node_modules/frontblock*; npm install" "update-frontblock": "rm -rf node_modules/frontblock*; npm install"
}, },
"repository": { "repository": {
-4
View File
@@ -1,4 +0,0 @@
import { Greeter } from '../index';
test('My Greeter', () => {
expect(Greeter('Carl')).toBe('Hello Carl');
});
@@ -1,13 +1,13 @@
'use strict' 'use strict'
import * as Logger from 'log4js' import * as Logger from 'log4js'
import { Plugin, socketioRPC } from 'frontblock-generic/Plugin'; import { Plugin, socketioRPC, ConfigLoader } from 'frontblock-generic/Plugin';
import { ErrorResponse, SuccessResponse } from 'frontblock-generic/Types'; import { ErrorResponse, SuccessResponse } from 'frontblock-generic/Types';
import { PluginManager } from './PluginManager' import { PluginManagerPlugin } from './PluginManager/Plugin'
import express = require('express'); import express = require('express');
import http = require('http'); import http = require('http');
import bsock = require('bsock'); import bsock = require('bsock');
import { type } from 'os';
const kfs = require("key-file-storage").default('kfs') const kfs = require("key-file-storage").default('kfs')
@@ -51,6 +51,8 @@ export type rpcInfo =
info: hookRPC | unhookRPC | callRPC info: hookRPC | unhookRPC | callRPC
} }
export type AdminConf = { httpPort: number}
/** /**
* FrontblockAdmin * FrontblockAdmin
* *
@@ -59,27 +61,29 @@ export type rpcInfo =
* *
* The list of available plugins is published via the frontblock API and downloaded via gitea-releases * The list of available plugins is published via the frontblock API and downloaded via gitea-releases
*/ */
export class FrontblockAdmin{ export class FrontblockAdmin extends ConfigLoader<AdminConf>{
private plugins: Plugin[] = []
private hookToUids:{[hookName:string]:string[]} = {} private hookToUids:{[hookName:string]:string[]} = {}
private express private express
private httpServer private httpServer
private io = bsock.createServer() private io = bsock.createServer()
private wsServer = http.createServer() private wsServer = http.createServer()
private pm: PluginManager private pm: PluginManagerPlugin
constructor(){ constructor(){
if(!('admin.conf' in kfs)){ super("FrontblockAdmin")
logger.info('Generating ./kfs/admin.conf') this.pm = new PluginManagerPlugin()
kfs['admin.conf'] = { httpPort: 8080 }
}
this.pm = new PluginManager()
this.initialize() this.initialize()
} }
getDefaultConfig(): AdminConf {
return {httpPort: 8080}
}
private async initialize(){ private async initialize(){
//this.startWebsocket() this.startWebsocket()
//this.startWebserver() this.startWebserver()
} }
private initApis(socket){ private initApis(socket){
@@ -104,14 +108,15 @@ export class FrontblockAdmin{
} }
] ]
//translate RPCs to socket-bound function metadata
this.plugins.forEach(plugin => { //translate RPCs to socket-bound function metadata
const pluginName = plugin.name const loadedPlugins = this.pm.getLoadedPlugins()
plugin.exportRPCs().forEach(rpc => { for(const name in loadedPlugins){
const info = this.rpcToRpcInfo(pluginName, rpc) loadedPlugins[name].exportRPCs().forEach(rpc => {
const info = this.rpcToRpcInfo(name, rpc)
rpcInfos.push(info) rpcInfos.push(info)
}) })
}) }
//Hook up all the functions //Hook up all the functions
for(const api of rpcInfos){ for(const api of rpcInfos){
@@ -161,7 +166,7 @@ export class FrontblockAdmin{
return return
} }
let port:number = kfs.FrontblockAdmin.httpPort let port:number = this.conf.httpPort
this.express = express() this.express = express()
this.express.use(express.static('static')) this.express.use(express.static('static'))
@@ -184,7 +189,7 @@ export class FrontblockAdmin{
private restartWebserver(port:number){ private restartWebserver(port:number){
this.stopWebserver() this.stopWebserver()
kfs['admin.conf'] = { httpPort: port } kfs[this.name+".conf"] = <AdminConf>{ httpPort: port }
this.startWebserver() this.startWebserver()
} }
+88
View File
@@ -0,0 +1,88 @@
import { default as PluginManager } from "./PluginManager";
import { Plugin } from "frontblock-generic/Plugin";
import * as Logger from 'log4js';
Logger.configure({
appenders:
{
"PluginManager/Plugin": { type: 'stdout' },
//app: { type: 'file', filename: 'application.log' }
},
categories:
{
default: { appenders: [ 'PluginManager/Plugin' ], level: 'debug' }
}
})
const logger = Logger.getLogger("PluginManager/Plugin")
export class PluginManagerPlugin extends PluginManager implements Plugin{
name: string;
constructor(){
super()
this.loadedPlugins[this.name] = this
}
exportRPCs(): import("frontblock-generic/Plugin").socketioRPC[] {
return [
{
name: "getAvailablePluginNames",
visibility: "private",
rpc: async() => { return await this.getAvailablePluginNames() },
type: 'call'
},
{
name: "getInstalledPlugins",
visibility: "private",
rpc: async() => { return await this.getInstalledPlugins() },
type: 'call'
},
{
name: "getLoadedPlugins",
visibility: "private",
rpc: async () => { return this.getLoadedPlugins() },
type: 'call'
},
{
name: "installPlugin",
visibility: "private",
rpc: async(pluginName:string) => { return await this.installPlugin(pluginName) },
type: 'call'
},
{
name: "uninstallPlugin",
visibility: "private",
rpc: async(pluginName:string) => { return await this.uninstallPlugin(pluginName) },
type: 'call'
},
{
name: "loadPlugin",
visibility: "private",
rpc: async(pluginName:string) => { return await this.loadPlugin(pluginName) },
type: 'call'
},
{
name: "unloadPlugin",
visibility: "private",
rpc: async(pluginName:string) => { return this.unloadPlugin(pluginName) },
type: 'call'
},
{
name: "updateConfig",
visibility: "private",
rpc: async(conf) => { return this.updateConfig(conf) },
type: 'call'
}
]
}
async start(): Promise<void> {
await this.installPlugin("admin") //self-update
}
stop(): void {
throw new Error("Method not implemented.");
}
}
@@ -5,33 +5,54 @@ import fetch = require('node-fetch');
import fs = require('fs'); import fs = require('fs');
import path = require('path') import path = require('path')
import unzip = require('unzip') import unzip = require('unzip')
import { Plugin, ConfigLoader } from 'frontblock-generic/Plugin';
import { SemVer } from "semver";
Logger.configure({ Logger.configure({
appenders: appenders:
{ {
"plugin-manager": { type: 'stdout' }, "PluginManager": { type: 'stdout' },
//app: { type: 'file', filename: 'application.log' } //app: { type: 'file', filename: 'application.log' }
}, },
categories: categories:
{ {
default: { appenders: [ 'plugin-manager' ], level: 'debug' } default: { appenders: [ 'PluginManager' ], level: 'debug' }
} }
}) })
const logger = Logger.getLogger("plugin-manager") const logger = Logger.getLogger("PluginManager")
type PluginVersion = {
installed?: SemVer,
cached?: SemVer,
loaded?: SemVer
}
export type PluginVersioning = {
[pluginName in string]?: PluginVersion | string
}
export type PluginMap = {[pluginName:string]: Plugin}
export class PluginManager{ export type PluginManagerConfig = {
resourceLocation: string,
installDir:string
}
private installDir: string export default class PluginManager extends ConfigLoader<PluginManagerConfig&PluginVersioning>{
private cacheDir: string private cacheDir: string
private loadedPlugins: string[] protected loadedPlugins: PluginMap = {}
constructor(installDir: string = './plugins'){ constructor(){
this.installDir = installDir super("PluginManager")
this.cacheDir = path.join(installDir, '.cache') this.cacheDir = path.join(this.conf.installDir, '.cache')
this.initialize() this.initialize()
this.loadPlugin("../../../../../../apiclient")
} }
getDefaultConfig(): PluginManagerConfig&PluginVersioning{
return {resourceLocation: "https://gitea.frontblock.me/api/v1/repos/fb-vendor/", installDir: "./plugins"}
}
private initialize() { private initialize() {
logger.info('Initializing manager') logger.info('Initializing manager')
@@ -39,9 +60,9 @@ export class PluginManager{
fs.access(this.cacheDir, async err => { fs.access(this.cacheDir, async err => {
if (err && err.code === 'ENOENT') { if (err && err.code === 'ENOENT') {
await fs.promises.mkdir(this.cacheDir, {recursive: true}) await fs.promises.mkdir(this.cacheDir, {recursive: true})
logger.info('Manager installed to ' + this.installDir) logger.info('Manager installed to ' + this.conf.installDir)
} else { } else {
logger.info('Manager already installed at ' + this.installDir) logger.info('Manager already installed at ' + this.conf.installDir)
} }
}) })
} catch (error) { } catch (error) {
@@ -49,7 +70,9 @@ export class PluginManager{
} }
} }
public async getAvailablePlugins(): Promise<string[] | void> {
public async getAvailablePluginNames(): Promise<string[]> {
//TODO query api
return [ return [
'apiclient', 'apiclient',
'paymentmanager', 'paymentmanager',
@@ -58,20 +81,21 @@ export class PluginManager{
] ]
} }
public async getInstalledPlugins(): Promise<string[] | void> { public async getInstalledPlugins(): Promise<string[]> {
try { try {
const installed = await fs.promises.readdir(this.installDir) const installed = await fs.promises.readdir(this.conf.installDir)
return installed.filter( entry => entry[0] != '.') return installed.filter( entry => entry[0] != '.')
} catch (error) { } catch (error) {
logger.error(error) logger.error(error)
return throw error
} }
} }
public async getLoadedPlugins(): Promise<string[] | void> { public getLoadedPlugins(): PluginMap {
return this.loadedPlugins return this.loadedPlugins
} }
private async downloadFile(url: URL, dest: string) { private async downloadFile(url: URL, dest: string) {
try { try {
fs.access(path.dirname(dest), err => { fs.access(path.dirname(dest), err => {
@@ -93,7 +117,9 @@ export class PluginManager{
private async downloadPlugin(pluginName: string) { private async downloadPlugin(pluginName: string) {
try { try {
const response = await fetch('https://gitea.frontblock.me/api/v1/repos/fb-vendor/' + pluginName + '/releases') logger.info("Downloading Plugin")
const response = await fetch(this.conf.resourceLocation + pluginName + '/releases')
if (!response.ok) throw new Error(response.statusText) if (!response.ok) throw new Error(response.statusText)
const releases = await response.json() const releases = await response.json()
@@ -116,21 +142,36 @@ export class PluginManager{
public async installPlugin(pluginName: string) { public async installPlugin(pluginName: string) {
try { try {
await this.downloadPlugin(pluginName) await this.downloadPlugin(pluginName)
//TODO do something
} catch (error) { } catch (error) {
logger.debug(error) logger.error(error)
} }
} }
public uninstallPlugin(pluginName: string) { public async uninstallPlugin(pluginName: string) {
//TODO check for exists
logger.info("Deleting plugin", pluginName)
await fs.promises.rmdir(path.join(this.conf.installDir,pluginName))
} }
public async loadPlugin() { public async loadPlugin(pluginName:string) {
const pth = path.join(this.conf.installDir, pluginName, "Plugin")
logger.info("Loading plugin from fs", pth)
const clazz = await import(pth)
let obj:Plugin = new clazz.default()
await obj.start()
this.loadedPlugins[pluginName] = obj
} }
public async unloadPlugin() { public unloadPlugin(pluginName:string):void {
if(this.loadedPlugins[pluginName] == null){
logger.warn("Cannt unload plugin: Unknown:", pluginName)
return
}
logger.info("Unloading plugin ", pluginName)
this.loadedPlugins[pluginName].stop()
delete this.loadedPlugins[pluginName]
} }
} }
-5
View File
@@ -1,5 +0,0 @@
export default class Controller {
private $rootScope;
static $inject: string[];
constructor($rootScope: any);
}
-8
View File
@@ -1,8 +0,0 @@
export default class Controller {
static $inject = ['$rootScope']
constructor(private $rootScope){
}
}
-7
View File
@@ -1,7 +0,0 @@
import './index.scss';
import rootCtrl from './controller';
declare const _default: {
template: string;
controller: typeof rootCtrl;
};
export default _default;
-34
View File
@@ -1,34 +0,0 @@
<div class="grid">
<aside class="sidenav">
<div class="sidenav__profile">
<a class="sidenav__brand-link" href="#">Front<span class="text-light">block</span></a>
<i class="fas fa-times sidenav__brand-close"></i>
</div>
<div class="row row--align-v-center row--align-h-center">
<ul class="navlist">
<li class="navlist__heading">wallets<i class="far fa-file-alt"></i></li>
<!--
<a ui-sref="btc" ui-sref-active="active">
<div class="navlist__subheading row row--align-v-center">
<span class="navlist__subheading-icon"><i class="fas fa-briefcase-medical"></i></span>
<span class="navlist__subheading-title">Bitcoin</span>
</div>
</a>
-->
<a ui-sref="todolist" ui-sref-active="active">
<div class="navlist__subheading row row--align-v-center">
<span class="navlist__subheading-icon"><i class="fas fa-users"></i></span>
<span class="navlist__subheading-title">ANOTHER ONE</span>
</div>
</a>
</ul>
</div>
</aside>
<main class="main">
<div ui-view class="view"></div>
</main>
<footer class="footer">
<p><span class="footer__copyright">&copy;</span> MIT license (open source)</p>
<p>by <a href="https://frontblock.me" target="_blank" class="footer__signature">Frontblock.me</a></p>
</footer>
</div>
-6
View File
@@ -1,6 +0,0 @@
.view {
height: 100%;
min-height: 300px;
}
-8
View File
@@ -1,8 +0,0 @@
const html = require('./index.html').default
import './index.scss'
import rootCtrl from './controller'
export default {
template: html,
controller: rootCtrl
}
-2
View File
@@ -1,2 +0,0 @@
declare const _default: string;
export default _default;
-7
View File
@@ -1,7 +0,0 @@
import angular = require('angular')
import Root from './Root'
export default angular
.module('components', [])
.component('root', Root)
.name
-5
View File
@@ -1,5 +0,0 @@
declare function routerRegister($urlRouterProvider: any, $stateProvider: any): void;
declare namespace routerRegister {
var $inject: string[];
}
export default routerRegister;
-34
View File
@@ -1,34 +0,0 @@
const routePathes: Array<string> = [
'todo-list'
]
let routes: Array<object> = []
routePathes.forEach(path => {
const parentRoutes = require(`../views/${path}/route`).default
const { children } = parentRoutes
let childRoutes: Array<object> = []
if (children && children.length > 0) {
childRoutes = children.map(childRoute => {
childRoute.name = `${parentRoutes.name}.${childRoute.name}`
return childRoute
})
}
delete parentRoutes.children
childRoutes.push(parentRoutes)
routes = routes.concat(childRoutes)
})
function routerRegister($urlRouterProvider, $stateProvider): void {
$urlRouterProvider.otherwise('views')
routes.forEach(route => {
$stateProvider.state(route)
})
}
routerRegister.$inject = ['$urlRouterProvider', '$stateProvider']
export default routerRegister
-2
View File
@@ -1,2 +0,0 @@
declare const _default: string;
export default _default;
-7
View File
@@ -1,7 +0,0 @@
import angular = require('angular')
import todosVisible from './todosVisible'
export default angular
.module('filters', [])
.filter('todosVisible', () => todosVisible)
.name
-1
View File
@@ -1 +0,0 @@
export default function (todos: any, type: string): Array<object>;
-12
View File
@@ -1,12 +0,0 @@
export default function (todos, type: string): Array<object> {
switch (type) {
case 'All':
return todos
case 'Todo':
return todos.filter(todo => !todo.complete)
case 'Done':
return todos.filter(todo => todo.complete)
default:
throw new Error('Unknown type ' + type)
}
}
+63
View File
@@ -0,0 +1,63 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"project": {
"name": "dashboard"
},
"apps": [
{
"root": "src",
"outDir": "dist",
"assets": [
"assets",
"favicon.ico"
],
"index": "index.html",
"main": "main.ts",
"polyfills": "polyfills.ts",
"test": "test.ts",
"tsconfig": "tsconfig.app.json",
"testTsconfig": "tsconfig.spec.json",
"prefix": "app",
"styles": [
"../node_modules/@clr/ui/clr-ui.min.css",
"styles.css"
],
"scripts": [
"../node_modules/systemjs/dist/system.js"
],
"environmentSource": "environments/environment.ts",
"environments": {
"dev": "environments/environment.ts",
"prod": "environments/environment.prod.ts"
}
}
],
"e2e": {
"protractor": {
"config": "./protractor.conf.js"
}
},
"lint": [
{
"project": "src/tsconfig.app.json",
"exclude": "**/node_modules/**"
},
{
"project": "src/tsconfig.spec.json",
"exclude": "**/node_modules/**"
},
{
"project": "e2e/tsconfig.e2e.json",
"exclude": "**/node_modules/**"
}
],
"test": {
"karma": {
"config": "./karma.conf.js"
}
},
"defaults": {
"styleExt": "css",
"component": {}
}
}
+13
View File
@@ -0,0 +1,13 @@
# Editor configuration, see http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false
+44
View File
@@ -0,0 +1,44 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/dist-server
/tmp
/out-tsc
# dependencies
/node_modules
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings
# e2e
/e2e/*.js
/e2e/*.map
# System Files
.DS_Store
Thumbs.db
+7
View File
@@ -0,0 +1,7 @@
# Dashboard
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.7.4.
## Development server
Run `yarn start` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
+14
View File
@@ -0,0 +1,14 @@
import { AppPage } from './app.po';
describe('dashboard App', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
});
it('should display welcome message', () => {
page.navigateTo();
expect(page.getParagraphText()).toEqual('Welcome to app!');
});
});
+11
View File
@@ -0,0 +1,11 @@
import { browser, by, element } from 'protractor';
export class AppPage {
navigateTo() {
return browser.get('/');
}
getParagraphText() {
return element(by.css('app-root h1')).getText();
}
}
+14
View File
@@ -0,0 +1,14 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"baseUrl": "./",
"module": "commonjs",
"target": "es5",
"types": [
"jasmine",
"jasminewd2",
"node"
]
}
}
+33
View File
@@ -0,0 +1,33 @@
// 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/cli'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular/cli/plugins/karma')
],
client:{
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
reports: [ 'html', 'lcovonly' ],
fixWebpackSourcePaths: true
},
angularCli: {
environment: 'dev'
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false
});
};
+13122
View File
File diff suppressed because it is too large Load Diff
+51
View File
@@ -0,0 +1,51 @@
{
"name": "dashboard",
"version": "0.0.0",
"license": "MIT",
"scripts": {
"ng": "ng",
"start": "ng serve --aot=true --proxy-config proxy.conf.json",
"build": "ng build --prod --aot=false",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
"private": true,
"dependencies": {
"@angular/animations": "^5.2.0",
"@angular/common": "^5.2.0",
"@angular/compiler": "^5.2.0",
"@angular/core": "^5.2.0",
"@angular/forms": "^5.2.0",
"@angular/http": "^5.2.0",
"@angular/platform-browser": "^5.2.0",
"@angular/platform-browser-dynamic": "^5.2.0",
"@angular/router": "^5.2.0",
"@clr/ui": "^0.11.15",
"core-js": "^2.4.1",
"rxjs": "^5.5.6",
"systemjs": "^0.21.3",
"zone.js": "^0.8.19"
},
"devDependencies": {
"@angular/cli": "~1.7.4",
"@angular/compiler-cli": "^5.2.0",
"@angular/language-service": "^5.2.0",
"@types/jasmine": "~2.8.3",
"@types/jasminewd2": "~2.0.2",
"@types/node": "~6.0.60",
"@types/systemjs": "^0.20.6",
"codelyzer": "^4.0.1",
"jasmine-core": "~2.8.0",
"jasmine-spec-reporter": "~4.2.1",
"karma": "~2.0.0",
"karma-chrome-launcher": "~2.2.0",
"karma-coverage-istanbul-reporter": "^1.2.1",
"karma-jasmine": "~1.1.0",
"karma-jasmine-html-reporter": "^0.2.2",
"protractor": "~5.1.2",
"ts-node": "~4.1.0",
"tslint": "~5.9.1",
"typescript": "~2.5.3"
}
}
+28
View File
@@ -0,0 +1,28 @@
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter } = require('jasmine-spec-reporter');
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./e2e/**/*.e2e-spec.ts'
],
capabilities: {
'browserName': 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function() {}
},
onPrepare() {
require('ts-node').register({
project: 'e2e/tsconfig.e2e.json'
});
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
}
};
+9
View File
@@ -0,0 +1,9 @@
{
"/widgets-repo/*": {
"target": "http://localhost:4201",
"secure": false,
"pathRewrite": {
"^/widgets-repo": ""
}
}
}
+15
View File
@@ -0,0 +1,15 @@
<div class="main-container">
<header class="header-6">
<div class="branding">
<a class="nav-link">
<span class="title">Dashboard and Widgets</span>
</a>
</div>
</header>
<div class="content-container">
<div class="content-area">
<app-dashboard></app-dashboard>
</div>
</div>
</div>
+8
View File
@@ -0,0 +1,8 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {}
+43
View File
@@ -0,0 +1,43 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { COMPILER_OPTIONS, CompilerFactory, Compiler } from '@angular/core';
import { JitCompilerFactory } from '@angular/platform-browser-dynamic';
import { AppComponent } from './app.component';
import { DashboardModule } from './dashboard/dashboard.module';
export function createCompiler(fn: CompilerFactory): Compiler {
return fn.createCompiler();
}
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
HttpClientModule,
DashboardModule
],
providers: [
{
provide: COMPILER_OPTIONS,
useValue: {},
multi: true
},
{
provide: CompilerFactory,
useClass: JitCompilerFactory,
deps: [COMPILER_OPTIONS]
},
{
provide: Compiler,
useFactory: createCompiler,
deps: [CompilerFactory]
}
],
bootstrap: [AppComponent]
})
export class AppModule { }
@@ -0,0 +1,12 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { DashboardComponent } from './dashboard/dashboard.component';
import { DashboardService } from './dashboard.service';
@NgModule({
imports: [CommonModule],
declarations: [DashboardComponent],
providers: [DashboardService],
exports: [DashboardComponent]
})
export class DashboardModule { }
@@ -0,0 +1,17 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { WidgetConfig } from './widget-config.model';
@Injectable()
export class DashboardService {
constructor(private http: HttpClient) { }
private readonly url = 'widgets-repo/widgets.config.json';
getWidgetConfigs(): Observable<WidgetConfig[]> {
return this.http.get<WidgetConfig[]>(this.url);
}
}
@@ -0,0 +1,3 @@
<div class="card-columns">
<div #content></div>
</div>
@@ -0,0 +1,71 @@
/**
* Set existing vendor modules into SystemJS registry.
* This way SystemJS won't make HTTP requests to fetch imported modules
* needed by the dynamicaly loaded Widgets.
*/
import { System } from 'systemjs';
declare const SystemJS: System;
import * as angularCore from '@angular/core';
import * as angularCommon from '@angular/common';
import * as angularCommonHttp from '@angular/common/http';
import * as angularForms from '@angular/forms';
import * as angularAnimations from '@angular/animations';
import * as angularPlatformBrowser from '@angular/platform-browser';
import * as angularPlatformBrowserDynamic from '@angular/platform-browser-dynamic';
SystemJS.set('@angular/core', SystemJS.newModule(angularCore));
SystemJS.set('@angular/common', SystemJS.newModule(angularCommon));
SystemJS.set('@angular/common/http', SystemJS.newModule(angularCommonHttp));
SystemJS.set('@angular/forms', SystemJS.newModule(angularForms));
SystemJS.set('@angular/animations', SystemJS.newModule(angularAnimations));
SystemJS.set('@angular/platform-browser', SystemJS.newModule(angularPlatformBrowser));
SystemJS.set('@angular/platform-browser-dynamic', SystemJS.newModule(angularPlatformBrowserDynamic));
SystemJS.config({ meta: { '*': { authorization: true } } });
/** --------- */
import { AfterViewInit, Component, Compiler, Injector, OnInit, ViewChild, ViewContainerRef } from '@angular/core';
import { DashboardService } from '../dashboard.service';
import { WidgetConfig } from '../widget-config.model';
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent implements AfterViewInit {
@ViewChild('content', { read: ViewContainerRef }) content: ViewContainerRef;
constructor(private compiler: Compiler, private dashboardService: DashboardService,
private injector: Injector) { }
ngAfterViewInit() {
this.loadWidgets();
}
private async loadWidgets() {
const widgets = await this.dashboardService.getWidgetConfigs().toPromise();
widgets.forEach((widget) => this.createWidget(widget));
}
private async createWidget(widget: WidgetConfig) {
// import external module bundle
console.log(`Importing module bundle: ${widget.moduleBundlePath}`);
const module = await SystemJS.import(widget.moduleBundlePath);
// compile module
const moduleFactory = await this.compiler.compileModuleAsync(module[widget.moduleName]);
// resolve component factory
const moduleRef = moduleFactory.create(this.injector);
const componentProvider = moduleRef.injector.get(widget.name);
const componentFactory = moduleRef.componentFactoryResolver.resolveComponentFactory(componentProvider);
// compile component
console.log(`Creating widget: ${widget.name}`);
this.content.createComponent(componentFactory);
}
}
@@ -0,0 +1,5 @@
export interface WidgetConfig {
name: string;
moduleBundlePath: string;
moduleName: string;
}
View File
@@ -0,0 +1,3 @@
export const environment = {
production: true
};
@@ -0,0 +1,8 @@
// The file contents for the current environment will overwrite these during build.
// The build system defaults to the dev environment which uses `environment.ts`, but if you do
// `ng build --env=prod` then `environment.prod.ts` will be used instead.
// The list of which env maps to which file can be found in `.angular-cli.json`.
export const environment = {
production: false
};
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

+14
View File
@@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Dashboard</title>
<base href="/">
<script src="FrontblockLib.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>
+12
View File
@@ -0,0 +1,12 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.log(err));
+79
View File
@@ -0,0 +1,79 @@
/**
* This file includes polyfills needed by Angular and is loaded before the app.
* You can add your own extra polyfills to this file.
*
* This file is divided into 2 sections:
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
* file.
*
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
*
* Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
*/
/***************************************************************************************************
* BROWSER POLYFILLS
*/
/** IE9, IE10 and IE11 requires all of the following polyfills. **/
// import 'core-js/es6/symbol';
// import 'core-js/es6/object';
// import 'core-js/es6/function';
// import 'core-js/es6/parse-int';
// import 'core-js/es6/parse-float';
// import 'core-js/es6/number';
// import 'core-js/es6/math';
// import 'core-js/es6/string';
// import 'core-js/es6/date';
// import 'core-js/es6/array';
// import 'core-js/es6/regexp';
// import 'core-js/es6/map';
// import 'core-js/es6/weak-map';
// import 'core-js/es6/set';
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js'; // Run `npm install --save classlist.js`.
/** IE10 and IE11 requires the following for the Reflect API. */
// import 'core-js/es6/reflect';
/** Evergreen browsers require these. **/
// Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
import 'core-js/es7/reflect';
/**
* Required to support Web Animations `@angular/platform-browser/animations`.
* Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation
**/
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/**
* By default, zone.js will patch all possible macroTask and DomEvents
* user can disable parts of macroTask/DomEvents patch by setting following flags
*/
// (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
// (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
// (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
/*
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
* with the following flag, it will bypass `zone.js` patch for IE/Edge
*/
// (window as any).__Zone_enable_cross_context_check = true;
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
import 'zone.js/dist/zone'; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/
+1
View File
@@ -0,0 +1 @@
/* You can add global styles to this file, and also import other style files */
+20
View File
@@ -0,0 +1,20 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
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);
+13
View File
@@ -0,0 +1,13 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/app",
"baseUrl": "./",
"module": "es2015",
"types": []
},
"exclude": [
"test.ts",
"**/*.spec.ts"
]
}
+19
View File
@@ -0,0 +1,19 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/spec",
"baseUrl": "./",
"module": "commonjs",
"types": [
"jasmine",
"node"
]
},
"files": [
"test.ts"
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}
+5
View File
@@ -0,0 +1,5 @@
/* SystemJS module definition */
declare var module: NodeModule;
interface NodeModule {
id: string;
}
+19
View File
@@ -0,0 +1,19 @@
{
"compileOnSave": false,
"compilerOptions": {
"outDir": "./static/out-tsc",
"sourceMap": true,
"declaration": false,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es5",
"typeRoots": [
"node_modules/@types"
],
"lib": [
"es2017",
"dom"
]
}
}
+143
View File
@@ -0,0 +1,143 @@
{
"rulesDirectory": [
"node_modules/codelyzer"
],
"rules": {
"arrow-return-shorthand": true,
"callable-types": true,
"class-name": true,
"comment-format": [
true,
"check-space"
],
"curly": true,
"deprecation": {
"severity": "warn"
},
"eofline": true,
"forin": true,
"import-blacklist": [
true,
"rxjs",
"rxjs/Rx"
],
"import-spacing": true,
"indent": [
true,
"spaces"
],
"interface-over-type-literal": true,
"label-position": true,
"max-line-length": [
true,
140
],
"member-access": false,
"member-ordering": [
true,
{
"order": [
"static-field",
"instance-field",
"static-method",
"instance-method"
]
}
],
"no-arg": true,
"no-bitwise": true,
"no-console": [
true,
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-construct": true,
"no-debugger": true,
"no-duplicate-super": true,
"no-empty": false,
"no-empty-interface": true,
"no-eval": true,
"no-inferrable-types": [
true,
"ignore-params"
],
"no-misused-new": true,
"no-non-null-assertion": true,
"no-shadowed-variable": true,
"no-string-literal": false,
"no-string-throw": true,
"no-switch-case-fall-through": true,
"no-trailing-whitespace": true,
"no-unnecessary-initializer": true,
"no-unused-expression": true,
"no-use-before-declare": true,
"no-var-keyword": true,
"object-literal-sort-keys": false,
"one-line": [
true,
"check-open-brace",
"check-catch",
"check-else",
"check-whitespace"
],
"prefer-const": true,
"quotemark": [
true,
"single"
],
"radix": true,
"semicolon": [
true,
"always"
],
"triple-equals": [
true,
"allow-null-check"
],
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
}
],
"unified-signatures": true,
"variable-name": false,
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type"
],
"directive-selector": [
true,
"attribute",
"app",
"camelCase"
],
"component-selector": [
true,
"element",
"app",
"kebab-case"
],
"no-output-on-prefix": true,
"use-input-property-decorator": true,
"use-output-property-decorator": true,
"use-host-property-decorator": true,
"no-input-rename": true,
"no-output-rename": true,
"use-life-cycle-interface": true,
"use-pipe-transform-interface": true,
"component-class-suffix": true,
"directive-class-suffix": true
}
}
File diff suppressed because it is too large Load Diff
-2
View File
@@ -1,2 +0,0 @@
/// <reference path="type.d.ts" />
import './index.scss';
-13
View File
@@ -1,13 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<script src="FrontblockLib.js"></script>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<root></root>
</body>
</html>
-897
View File
@@ -1,897 +0,0 @@
html, body {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: "Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif; }
a {
text-decoration: none; }
.break-word {
word-wrap: break-word;
}
.text-light {
font-weight: 300; }
.monospace {
font-family: monospace;
font-size: 90%
}
.text-bold {
font-weight: bold; }
.row {
}
.row--align-v-center {
align-items: center; }
.row--align-h-center {
justify-content: center; }
.grid {
position: relative;
display: grid;
grid-template-columns: 100%;
grid-template-rows: 50px 1fr 50px;
grid-template-areas: 'header' 'main' 'footer';
height: 100vh;
overflow-x: hidden; }
.grid--noscroll {
overflow-y: hidden; }
.header {
grid-area: header;
display: flex;
align-items: center;
justify-content: space-between;
background-color: #F9FAFC; }
.header__menu {
position: fixed;
padding: 13px;
left: 12px;
background-color: #DADAE3;
border-radius: 50%;
z-index: 1; }
.header__menu:hover {
cursor: pointer; }
.header__search {
margin-left: 55px;
font-size: 20px;
color: #777; }
.header__input {
border: none;
background: transparent;
padding: 12px;
font-size: 20px;
color: #777; }
.header__input:focus {
outline: none;
border: none; }
.header__avatar {
background-size: cover;
background-repeat: no-repeat;
border-radius: 50%;
border: 2px solid rgba(255, 255, 255, 0.2);
position: relative;
margin: 0 26px;
width: 35px;
height: 35px;
cursor: pointer; }
.header__avatar:after {
position: absolute;
content: "";
width: 6px;
height: 6px;
background: none;
border-left: 2px solid #777;
border-bottom: 2px solid #777;
transform: rotate(-45deg) translateY(-50%);
top: 50%;
right: -18px; }
.dropdown {
position: absolute;
top: 54px;
right: -16px;
width: 220px;
height: auto;
z-index: 1;
background-color: #fff;
border-radius: 4px;
visibility: hidden;
opacity: 0;
transform: translateY(-10px);
transition: all .3s;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 0 1px rgba(0, 0, 0, 0.08); }
.dropdown__list {
margin: 0;
padding: 0;
list-style-type: none; }
.dropdown__list-item {
padding: 12px 24px;
color: #777;
text-transform: capitalize; }
.dropdown__list-item:hover {
background-color: rgba(0, 0, 0, 0.1); }
.dropdown__icon {
color: #1BBAE1; }
.dropdown__title {
margin-left: 10px; }
.dropdown:before {
position: absolute;
content: "";
top: -6px;
right: 30px;
width: 0;
height: 0;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-bottom: 6px solid #FFF; }
.dropdown--active {
visibility: visible;
opacity: 1;
transform: translateY(0); }
.sidenav {
position: fixed;
grid-area: sidenav;
height: 100%;
overflow-y: auto;
background-color: #394263;
color: #FFF;
width: 220px;
transform: translateX(-245px);
transition: all .6s ease-in-out;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 0 1px rgba(0, 0, 0, 0.08);
z-index: 2; }
.sidenav__brand {
position: relative;
display: flex;
align-items: center;
padding: 0 16px;
height: 50px;
background-color: rgba(0, 0, 0, 0.15); }
.sidenav__brand-icon {
margin-top: 2px;
font-size: 14px;
color: rgba(255, 255, 255, 0.5); }
.sidenav__brand-close {
position: absolute;
right: 8px;
top: 8px;
visibility: visible;
color: rgba(255, 255, 255, 0.5);
cursor: pointer; }
.sidenav__brand-link {
font-size: 18px;
font-weight: bold;
color: #FFF;
margin: 0 15px;
letter-spacing: 1.5px; }
.sidenav__profile {
display: flex;
align-items: center;
min-height: 90px;
background-color: rgba(255, 255, 255, 0.1); }
.sidenav__profile-avatar {
background-size: cover;
background-repeat: no-repeat;
border-radius: 50%;
border: 2px solid rgba(255, 255, 255, 0.2);
height: 64px;
width: 64px;
margin: 0 15px; }
.sidenav__profile-title {
font-size: 17px;
letter-spacing: 1px; }
.sidenav__arrow {
position: absolute;
content: "";
width: 6px;
height: 6px;
top: 50%;
right: 20px;
border-left: 2px solid rgba(255, 255, 255, 0.5);
border-bottom: 2px solid rgba(255, 255, 255, 0.5);
transform: translateY(-50%) rotate(225deg); }
.sidenav__sublist {
list-style-type: none;
margin: 0;
padding: 10px 0 0; }
.sidenav--active {
transform: translateX(0); }
.navlist {
width: 220px;
padding: 0;
margin: 0;
background-color: #394263;
list-style-type: none; }
.navlist__heading {
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 16px 3px;
color: rgba(255, 255, 255, 0.5);
text-transform: uppercase;
font-size: 15px; }
.navlist__subheading {
position: relative;
padding: 10px 30px;
color: #fff;
font-size: 16px;
text-transform: capitalize; }
.navlist__subheading-icon {
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
color: rgba(255, 255, 255, 0.5);
width: 12px; }
.navlist__subheading-title {
margin: 0 15px; }
.navlist__subheading:after {
position: absolute;
content: "";
height: 6px;
width: 6px;
top: 17px;
right: 25px;
border-left: 1px solid rgba(255, 255, 255, 0.5);
border-bottom: 1px solid rgba(255, 255, 255, 0.5);
transform: rotate(225deg);
transition: all .2s; }
.navlist__subheading:hover {
background-color: #303753;
cursor: pointer; }
.navlist__subheading--open {
background-color: #303753; }
.navlist__subheading--open:after {
transform: rotate(315deg); }
.navlist .sublist {
padding: 0;
margin: 0;
list-style-type: none;
background-color: #262c43;
visibility: visible;
overflow: hidden;
max-height: 220px;
transition: all .4s ease-in-out; }
.navlist .sublist__item {
padding: 8px;
text-transform: capitalize;
padding: 8px 30px;
color: #D3D3D3; }
.navlist .sublist__item:first-child {
padding-top: 15px; }
.navlist .sublist__item:hover {
background-color: rgba(255, 255, 255, 0.1);
cursor: pointer; }
.navlist .sublist--hidden {
visibility: hidden;
max-height: 0; }
.errorDisplay {
background: rgba(180, 55, 87, 0.5);
padding: 10px;
}
.main {
grid-area: main;
background-color: #EAEDF1;
color: #394263; }
.main__cards {
display: block;
column-count: 1;
column-gap: 20px;
margin: 20px; }
.main-header {
position: relative;
display: flex;
justify-content: space-between;
height: 250px;
color: #FFF;
background-size: cover;
margin-bottom: 20px; }
.main-header__intro-wrapper {
display: flex;
flex: 1;
flex-direction: column;
align-items: center;
justify-content: space-between;
height: 160px;
padding: 12px 30px;
background: rgba(255, 255, 255, 0.12);
font-size: 26px;
letter-spacing: 1px; }
.main-header__welcome {
display: flex;
flex-direction: column;
align-items: center; }
.main-header__welcome-title {
margin-bottom: 8px;
font-size: 26px; }
.main-header__welcome-subtitle {
font-size: 18px; }
.quickview {
display: grid;
grid-auto-flow: column;
grid-gap: 60px; }
.quickview__item {
display: flex;
align-items: center;
flex-direction: column; }
.quickview__item-total {
margin-bottom: 2px;
font-size: 32px; }
.quickview__item-description {
font-size: 16px;
text-align: center; }
.main-overview {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(265px, 1fr));
grid-auto-rows: 94px;
grid-gap: 30px;
margin: 20px; }
.overviewcard {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px;
background-color: #FFF;
transform: translateY(0);
transition: all .3s; }
.overviewcard-icon {
display: flex;
align-items: center;
justify-content: center;
height: 60px;
width: 60px;
border-radius: 50%;
font-size: 21px;
color: #fff; }
.overviewcard-icon--document {
background-color: #e67e22; }
.overviewcard-icon--calendar {
background-color: #27ae60; }
.overviewcard-icon--mail {
background-color: #e74c3c; }
.overviewcard-icon--photo {
background-color: #af64cc; }
.overviewcard-description {
display: flex;
flex-direction: column;
align-items: center; }
.overviewcard-title {
font-size: 18px;
color: #1BBAE1;
margin: 0; }
.overviewcard-subtitle {
margin: 2px;
color: #777; }
.overviewcard:hover {
transform: translateY(-3px);
box-shadow: 0 5px 5px rgba(0, 0, 0, 0.1);
cursor: pointer; }
.card {
display: flex;
flex-direction: column;
width: 100%;
background-color: #fff;
margin-bottom: 20px;
-webkit-column-break-inside: avoid; }
.card__header {
display: flex;
align-items: center;
justify-content: space-between;
height: 50px;
background-color: #394263;
color: #FFF; }
.card__header-title {
margin: 0 20px;
font-size: 20px;
letter-spacing: 1.2px; }
.card__header-link {
font-size: 16px;
color: #1BBAE1;
letter-spacing: normal;
display: inline-block; }
.card__main {
position: relative;
padding-right: 20px;
background-color: #FFF; }
.card__main:after {
content: "";
position: absolute;
top: 0;
left: 120px;
bottom: 0;
width: 2px;
background-color: #f0f0f0; }
.card__secondary {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
grid-auto-rows: 100px;
grid-gap: 25px;
padding: 20px;
background-color: #FFF; }
.card__photo {
background-size: cover;
background-repeat: no-repeat;
background-color: slategray;
transform: scale(1);
transition: transform .3s ease-in-out;
width: 100%;
height: 100%; }
.card__photo:hover {
transform: scale(1.1);
cursor: pointer; }
.card__photo-wrapper {
overflow: hidden; }
.card__row {
position: relative;
display: flex;
flex: 1;
margin: 15px 15px 15px; }
.card__icon {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
content: "";
width: 30px;
height: 30px;
top: 0;
left: 121px;
transform: translateX(-50%);
border-radius: 50%;
color: #FFF;
background-color: #1BBAE1;
z-index: 1; }
.card__row:nth-child(even) .card__icon {
background-color: #e74c3c; }
.card__time {
display: flex;
flex: 1;
justify-content: flex-end;
max-width: 80px;
margin-left: 15px;
text-align: right;
font-size: 14px;
line-height: 2; }
.card__detail {
display: flex;
flex: 1;
flex-direction: column;
padding-left: 12px;
margin-left: 48px;
transform: translateX(0);
transition: all .3s; }
.card__detail:hover {
background-color: #f0f0f0;
transform: translateX(4px);
cursor: pointer; }
.card__source {
line-height: 1.8;
color: #1BBAE1; }
.card__note {
margin: 10px 0;
color: #777; }
.card--finance {
position: relative; }
.settings {
display: flex;
margin: 8px;
align-self: flex-start;
background-color: rgba(255, 255, 255, 0.5);
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 2px; }
.settings__block {
display: flex;
align-items: center;
justify-content: center;
padding: 4px;
color: #394263;
font-size: 11px; }
.settings__block:not(:last-child) {
border-right: 1px solid rgba(0, 0, 0, 0.1); }
.settings__icon {
padding: 0px 3px;
font-size: 12px; }
.settings__icon:hover {
background-color: rgba(255, 255, 255, 0.8);
cursor: pointer; }
.settings:hover {
background-color: #fff;
cursor: pointer; }
.documents {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(105px, 1fr));
grid-auto-rows: 214px;
grid-gap: 12px;
height: auto;
background-color: #FFF; }
.document {
display: flex;
align-items: center;
justify-content: center;
margin: 15px 0 0;
flex-direction: column; }
.document__img {
width: 105px;
height: 136px;
background-size: cover;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.12), 0 1px 5px 0 rgba(0, 0, 0, 0.2);
cursor: pointer;
transition: transform .3s ease; }
.document__img:hover {
transform: translateY(-4px); }
.document__title {
margin: 8px 0 2px;
color: #777; }
.document__date {
font-size: 10px; }
#chartdiv {
width: 100%;
height: 300px;
font-size: 11px;
min-width: 0; }
.footer {
grid-area: footer;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 16px;
color: #777;
background-color: #FFF; }
.footer__copyright {
color: #1BBAE1; }
.footer__icon {
color: #e74c3c; }
.footer__signature {
color: #1BBAE1;
cursor: pointer;
font-weight: bold; }
@media only screen and (min-width: 46.875em) {
.grid {
display: grid;
grid-template-columns: 220px calc(100% - 220px);
grid-template-rows: 0px 1fr 50px;
grid-template-areas: 'sidenav header' 'sidenav main' 'sidenav footer';
height: 100vh; }
.grid__no-head {
display: grid;
grid-template-columns: 220px calc(100% - 220px);
grid-template-rows: 0px 1fr 0px;
grid-template-areas: 'sidenav header' 'sidenav main' 'sidenav footer';
height: calc(100%); }
.sidenav {
position: relative;
transform: translateX(0); }
.sidenav__brand-close {
visibility: hidden; }
.main-header__intro-wrapper {
padding: 0 30px; }
.header__menu {
display: none; }
.header__search {
margin-left: 20px; }
.header__avatar {
width: 40px;
height: 40px; } }
@media only screen and (min-width: 65.625em) {
.main__cards {
column-count: 2; }
.main-header__intro-wrapper {
flex-direction: row; }
.main-header__welcome {
align-items: flex-start; } }
table {
border-spacing: 1;
border-collapse: collapse;
background: white;
overflow: hidden;
width: 100%;
margin: 0 auto;
position: relative;
}
table * {
position: relative;
}
table td, table th {
padding-left: 8px;
}
table thead tr {
height: 50px;
background: #394263;
}
table tbody tr {
height: 50px;
}
table tbody tr:last-child {
border: 0;
}
table td, table th {
text-align: left;
}
table td.l, table th.l {
text-align: right;
}
table td.c, table th.c {
text-align: center;
}
table td.r, table th.r {
text-align: center;
}
.table100-head th{
color: #fff;
line-height: 1.2;
font-weight: unset;
}
tbody tr:nth-child(even) {
background-color: #f5f5f5;
}
tbody tr {
font-size: 15px;
color: #808080;
line-height: 1.2;
font-weight: unset;
}
tbody tr:hover {
color: #555555;
background-color: #f5f5f5;
cursor: pointer;
}
.column1 {
padding-left: 40px;
width: 280px;
}
.column2 {
width: 280px;
}
.column3 {
width: 280px;
}
.column4 {
width: 280px;
}
.column5 {
width: 170px;
text-align: right;
}
.column6 {
width: 222px;
text-align: right;
padding-right: 62px;
}
@media screen and (max-width: 1050px) {
table {
display: block;
}
table > *, table tr, table td, table th {
display: block;
}
table thead {
display: none;
}
table tbody tr {
height: auto;
padding: 37px 0;
}
table tbody tr td {
padding-left: 40% !important;
margin-bottom: 24px;
}
table tbody tr td:last-child {
margin-bottom: 0;
}
table tbody tr td:before {
font-size: 14px;
color: #999999;
line-height: 1.2;
font-weight: unset;
position: absolute;
width: 40%;
left: 30px;
top: 0;
}
table.t1 tbody tr td:nth-child(1):before {
content: "Address";
}
table.t1 tbody tr td:nth-child(2):before {
content: "Balance";
}
table.t1 tbody tr td:nth-child(3):before {
content: "Unconfirmed";
}
table.t1 tbody tr td:nth-child(4):before {
content: "Total";
}
table.t2 tbody tr td:nth-child(1):before {
content: "Type";
}
table.t2 tbody tr td:nth-child(2):before {
content: "Value";
}
table.t3 tbody tr:nth-child(1) td:nth-child(1) {
display: none;
}
table.t3 tbody tr:nth-child(1) td:nth-child(2):before {
content: "To";
}
table.t3 tbody tr:nth-child(2) td:nth-child(1){
display: none;
}
table.t3 tbody tr:nth-child(2) td:nth-child(2):before {
content: "Amount";
}
.column4,
.column5,
.column6 {
text-align: left;
}
.column4,
.column5,
.column6,
.column1,
.column2,
.column3 {
width: 100%;
}
tbody tr {
font-size: 14px;
}
}
@media (max-width: 576px) {
.container-table100 {
padding-left: 15px;
padding-right: 15px;
}
}
.qtip {
display: inline-block;
position: relative;
cursor: pointer;
color: #3bb4e5;
border-bottom: 0.05em dotted #3bb4e5;
box-sizing: border-box;
font-style: normal;
transition:all .25s ease-in-out;
z-index: 10000
}
.qtip:hover {color:#069;border-bottom:0.05em dotted #069}
/*the tip*/
.qtip:before {
content: attr(data-tip);
font-size: 14px;
position: absolute;
background: rgba(10, 20, 30, 0.85);
color: #fff;
line-height: 1.2em;
padding: 0.5em;
font-style: normal;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
min-width: 120px;
text-align: center;
opacity: 0;
visibility: hidden;
transition: all .3s ease-in-out;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5);
font-family: sans-serif;
letter-spacing: 0;
font-weight: 600
}
.qtip:after {
width: 0;
height: 0;
border-style: solid;
content: '';
position: absolute;
opacity: 0;
visibility: hidden;
transition: all .3s ease-in-out
}
.qtip:hover:before,
.qtip:hover:after {
visibility: visible;
opacity: 1
}
/*top*/
.qtip.tip-top:before {
top: 0;
left: 50%;
transform: translate(-50%, calc(-100% - 8px));
box-sizing: border-box;
border-radius: 3px;
}
.qtip.tip-top:after {
border-width: 8px 8px 0 8px;
border-color: rgba(10, 20, 30, 0.85) transparent transparent transparent;
top: -8px;
left: 50%;
transform: translate(-50%, 0);
}
/*bottom*/
.qtip.tip-bottom:before {
bottom: 0;
left: 50%;
transform: translate(-50%, calc(100% + 8px));
box-sizing: border-box;
border-radius: 3px;
}
.qtip.tip-bottom:after {
border-width: 0 8px 8px 8px;
border-color: transparent transparent rgba(10, 20, 30, 0.85) transparent;
bottom: -8px;
left: 50%;
transform: translate(-50%, 0);
}
/*left*/
.qtip.tip-left:before {
left: 0;
top: 50%;
transform: translate(calc(-100% - 8px), -50%);
box-sizing: border-box;
border-radius: 3px;
}
.qtip.tip-left:after {
border-width: 8px 0 8px 8px;
border-color: transparent transparent transparent rgba(10, 20, 30, 0.85);
left: -8px;
top: 50%;
transform: translate(0, -50%);
}
/*right*/
.qtip.tip-right:before {
right: 0;
top: 50%;
transform: translate(calc(100% + 8px), -50%);
box-sizing: border-box;
border-radius: 3px;
}
.qtip.tip-right:after {
border-width: 8px 8px 8px 0;
border-color: transparent rgba(10, 20, 30, 0.85) transparent transparent;
right: -8px;
top: 50%;
transform: translate(0, -50%);
}
-22
View File
@@ -1,22 +0,0 @@
import './index.scss'
import angular = require('angular')
import uiRouter from '@uirouter/angularjs'
import router from './config/routes.config'
import components from './components'
import views from './views'
import filters from './filters'
angular
.module('app', [
uiRouter,
components,
views,
filters
])
.config(router)
angular.bootstrap(document, ['app'])
if (module.hot) {
module.hot.accept()
}
-7
View File
@@ -1,7 +0,0 @@
class Log {
info(info) {
alert(info)
}
}
export default Log
-2
View File
@@ -1,2 +0,0 @@
declare const _default: string;
export default _default;
-7
View File
@@ -1,7 +0,0 @@
import angular = require('angular')
import TodoList from './todo-list'
export default angular
.module('wallets', [])
.component('todolist', TodoList)
.name
-13
View File
@@ -1,13 +0,0 @@
export default class TodoListCtrl {
private bip32;
static $inject: string[];
todos: any[];
id: number;
todo: string;
todoTypes: string[];
visibleType: string;
constructor(bip32: any);
handleSubmit(e: any): void;
toggleTodo(curTodo: any): void;
toggleType(type: string): void;
}
-23
View File
@@ -1,23 +0,0 @@
export default class TodoListCtrl {
static $inject = ['bip32']
todos = []
id = 0
todo: string
todoTypes = ['All', 'Todo', 'Done']
visibleType = 'All'
constructor(private bip32) {
}
handleSubmit(e): void {
if (e.keyCode !== 13) {
return
}
}
toggleType(type: string): void {
this.visibleType = type
}
}
-7
View File
@@ -1,7 +0,0 @@
import ctrl from './controller';
import './index.scss';
declare const _default: {
template: string;
controller: typeof ctrl;
};
export default _default;
-9
View File
@@ -1,9 +0,0 @@
<div class="todo-list">
<ul class="todos-type">
<li ng-repeat="type in $ctrl.todoTypes" ng-click="$ctrl.toggleType(type)" ng-class="{active:type===$ctrl.visibleType}">{{ type }}</li>
</ul>
<input type="text" class="input" ng-model="$ctrl.todo" ng-keyup="$ctrl.handleSubmit($event)" />
<ul class="todos">
<li ng-repeat="todo in $ctrl.todos | todosVisible:$ctrl.visibleType" ng-click="$ctrl.toggleTodo(todo)" ng-style="{textDecoration: todo.complete ? 'line-through' : 'none'}">{{ todo.text }}</li>
</ul>
</div>
-38
View File
@@ -1,38 +0,0 @@
.todo-list {
.input {
box-sizing: border-box;
display: block;
width: 200px;
margin: 20px auto;
padding: 5px 8px;
background-color: #fff;
border: 1px solid #d9d9d9;
border-radius: 4px;
transition: all .3s;
}
@at-root {
.todos-type {
display: table;
margin: 0 auto;
li {
display: table-cell;
padding: 3px 8px;
cursor: pointer;
&:hover {
color: #108ee9;
}
&.active {
color: #108ee9;
}
}
}
.todos {
width: 200px;
margin: 0 auto;
li {
line-height: 1.8;
cursor: pointer;
}
}
}
}
-8
View File
@@ -1,8 +0,0 @@
import ctrl from './controller'
const html = require('./index.html').default
import './index.scss'
export default {
template: html,
controller: ctrl
}
-6
View File
@@ -1,6 +0,0 @@
declare const _default: {
name: string;
url: string;
component: string;
};
export default _default;
-5
View File
@@ -1,5 +0,0 @@
export default {
name: 'todolist',
url: '/todolist',
component: 'todolist'
}
+2 -2
View File
@@ -137,7 +137,7 @@ const angularPage = {
const FrontblockLib = { const FrontblockLib = {
mode: 'production', mode: 'production',
entry: path.resolve(__dirname, 'lib/backendsrc/FrontblockLib.js'), entry: path.resolve(__dirname, 'lib/FrontblockLib.js'),
output: { output: {
path: path.resolve(__dirname, 'static'), path: path.resolve(__dirname, 'static'),
filename: 'FrontblockLib.js', filename: 'FrontblockLib.js',
@@ -180,6 +180,6 @@ const angularPage = {
module.exports = [ module.exports = [
Object.assign({}, angularPage), //Object.assign({}, angularPage),
Object.assign({}, FrontblockLib), Object.assign({}, FrontblockLib),
]; ];
+1 -1
View File
@@ -8,6 +8,6 @@
"outDir": "./lib", "outDir": "./lib",
"strict": true "strict": true
}, },
"include": ["backendsrc", "src"], "include": ["src/backend"],
"exclude": ["node_modules", "**/__tests__/*"], "exclude": ["node_modules", "**/__tests__/*"],
} }