mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-09-21 15:38:40 +03:00
community: learn: add angular-graphql tutorial (#2624)
This commit is contained in:
parent
18822ded9a
commit
9e18f0d297
@ -0,0 +1,50 @@
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: angular-apollo
|
||||
name: angular-apollo
|
||||
namespace: default
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: angular-apollo
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxUnavailable: 0
|
||||
maxSurge: 100%
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: angular-apollo
|
||||
spec:
|
||||
containers:
|
||||
- image: hasura/base-git-image:0.7
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: angular-apollo
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
protocol: TCP
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /graphql/angular-apollo/introduction
|
||||
port: 8080
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
app: angular-apollo
|
||||
name: angular-apollo
|
||||
namespace: default
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
protocol: TCP
|
||||
targetPort: 8080
|
||||
selector:
|
||||
app: angular-apollo
|
||||
type: ClusterIP
|
@ -37,10 +37,18 @@ data:
|
||||
/graphql/reason-react-apollo/boilerplate.zip https://graphql-engine-cdn.hasura.io/learn-hasura/boilerplates/reason-react-apollo/boilerplate.zip
|
||||
}
|
||||
|
||||
redir 301 {
|
||||
/graphql/angular-apollo/boilerplate.zip https://graphql-engine-cdn.hasura.io/learn-hasura/boilerplates/angular-apollo/boilerplate.zip
|
||||
}
|
||||
|
||||
redir 301 {
|
||||
/graphql/flutter-graphql/boilerplate.zip https://graphql-engine-cdn.hasura.io/learn-hasura/boilerplates/flutter-graphql/boilerplate.zip
|
||||
}
|
||||
|
||||
redir 301 {
|
||||
/graphql/angular-apollo/ /graphql/angular-apollo/introduction
|
||||
}
|
||||
|
||||
redir 301 {
|
||||
/graphql/flutter-graphql/ /graphql/flutter-graphql/introduction
|
||||
}
|
||||
@ -129,6 +137,8 @@ data:
|
||||
|
||||
proxy /graphql/reason-react-apollo reason-react-apollo
|
||||
|
||||
proxy /graphql/angular-apollo angular-apollo
|
||||
|
||||
proxy /graphql/flutter-graphql flutter-graphql
|
||||
|
||||
proxy /graphql hasura/v1/graphql {
|
||||
|
@ -69,6 +69,11 @@ spec:
|
||||
name: reason-react-apollo
|
||||
path: ./community/learn/graphql-tutorials/tutorials/reason-react-apollo/tutorial-site
|
||||
name: reason-react-apollo
|
||||
- containers:
|
||||
- dockerfile: ./community/learn/graphql-tutorials/tutorials/angular-apollo/tutorial-site/Dockerfile
|
||||
name: angular-apollo
|
||||
path: ./community/learn/graphql-tutorials/tutorials/angular-apollo/tutorial-site
|
||||
name: angular-apollo
|
||||
- containers:
|
||||
- dockerfile: ./community/learn/graphql-tutorials/tutorials/flutter-graphql/tutorial-site/Dockerfile
|
||||
name: flutter-graphql
|
||||
|
@ -8,7 +8,7 @@ import GithubLink from '../../src/GithubLink.js'
|
||||
|
||||
In this section, we will learn more about GraphQL Queries and integrate with the android app UI.
|
||||
|
||||
### Create `.graphql` files with your queries or mutations
|
||||
### Create .graphql files with your queries or mutations
|
||||
|
||||
Apollo android generates code from queries and mutations contained in `.graphql` files in your target. Let's define a graphql query to fetch the required data. We define all our queries and mutations in `api.graphql` file.
|
||||
|
||||
|
@ -0,0 +1,13 @@
|
||||
# Editor configuration, see https://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
|
46
community/learn/graphql-tutorials/tutorials/angular-apollo/app-boilerplate/.gitignore
vendored
Normal file
46
community/learn/graphql-tutorials/tutorials/angular-apollo/app-boilerplate/.gitignore
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# compiled output
|
||||
/dist
|
||||
/tmp
|
||||
/out-tsc
|
||||
# Only exists if Bazel was run
|
||||
/bazel-out
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# profiling files
|
||||
chrome-profiler-events.json
|
||||
speed-measure-plugin.json
|
||||
|
||||
# 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
|
||||
.history/*
|
||||
|
||||
# misc
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
/coverage
|
||||
/libpeerconnection.log
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
testem.log
|
||||
/typings
|
||||
|
||||
# System Files
|
||||
.DS_Store
|
||||
Thumbs.db
|
@ -0,0 +1,120 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"app-boilerplate": {
|
||||
"projectType": "application",
|
||||
"schematics": {},
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"prefix": "app",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"outputPath": "dist/app-boilerplate",
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"aot": false,
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.css"
|
||||
],
|
||||
"scripts": []
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"extractCss": true,
|
||||
"namedChunks": false,
|
||||
"aot": true,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false,
|
||||
"buildOptimizer": true,
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "2mb",
|
||||
"maximumError": "5mb"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"options": {
|
||||
"browserTarget": "app-boilerplate:build"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "app-boilerplate:build:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "app-boilerplate:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"main": "src/test.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"karmaConfig": "karma.conf.js",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.css"
|
||||
],
|
||||
"scripts": []
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"options": {
|
||||
"tsConfig": [
|
||||
"tsconfig.app.json",
|
||||
"tsconfig.spec.json",
|
||||
"e2e/tsconfig.json"
|
||||
],
|
||||
"exclude": [
|
||||
"**/node_modules/**"
|
||||
]
|
||||
}
|
||||
},
|
||||
"e2e": {
|
||||
"builder": "@angular-devkit/build-angular:protractor",
|
||||
"options": {
|
||||
"protractorConfig": "e2e/protractor.conf.js",
|
||||
"devServerTarget": "app-boilerplate:serve"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"devServerTarget": "app-boilerplate:serve:production"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}},
|
||||
"defaultProject": "app-boilerplate"
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
|
||||
# For additional information regarding the format and rule options, please see:
|
||||
# https://github.com/browserslist/browserslist#queries
|
||||
|
||||
# You can see what browsers were selected by your queries by running:
|
||||
# npx browserslist
|
||||
|
||||
> 0.5%
|
||||
last 2 versions
|
||||
Firefox ESR
|
||||
not dead
|
||||
not IE 9-11 # For IE 9-11 support, remove 'not'.
|
@ -0,0 +1,32 @@
|
||||
// @ts-check
|
||||
// Protractor configuration file, see link for more information
|
||||
// https://github.com/angular/protractor/blob/master/lib/config.ts
|
||||
|
||||
const { SpecReporter } = require('jasmine-spec-reporter');
|
||||
|
||||
/**
|
||||
* @type { import("protractor").Config }
|
||||
*/
|
||||
exports.config = {
|
||||
allScriptsTimeout: 11000,
|
||||
specs: [
|
||||
'./src/**/*.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: require('path').join(__dirname, './tsconfig.json')
|
||||
});
|
||||
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
|
||||
}
|
||||
};
|
@ -0,0 +1,23 @@
|
||||
import { AppPage } from './app.po';
|
||||
import { browser, logging } from 'protractor';
|
||||
|
||||
describe('workspace-project App', () => {
|
||||
let page: AppPage;
|
||||
|
||||
beforeEach(() => {
|
||||
page = new AppPage();
|
||||
});
|
||||
|
||||
it('should display welcome message', () => {
|
||||
page.navigateTo();
|
||||
expect(page.getTitleText()).toEqual('Welcome to app-boilerplate!');
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
// Assert that there are no errors emitted from the browser
|
||||
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
|
||||
expect(logs).not.toContain(jasmine.objectContaining({
|
||||
level: logging.Level.SEVERE,
|
||||
} as logging.Entry));
|
||||
});
|
||||
});
|
@ -0,0 +1,11 @@
|
||||
import { browser, by, element } from 'protractor';
|
||||
|
||||
export class AppPage {
|
||||
navigateTo() {
|
||||
return browser.get(browser.baseUrl) as Promise<any>;
|
||||
}
|
||||
|
||||
getTitleText() {
|
||||
return element(by.css('app-root h1')).getText() as Promise<string>;
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../out-tsc/e2e",
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"types": [
|
||||
"jasmine",
|
||||
"jasminewd2",
|
||||
"node"
|
||||
]
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
// 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/app-boilerplate'),
|
||||
reports: ['html', 'lcovonly', 'text-summary'],
|
||||
fixWebpackSourcePaths: true
|
||||
},
|
||||
reporters: ['progress', 'kjhtml'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['Chrome'],
|
||||
singleRun: false,
|
||||
restartOnFileChange: true
|
||||
});
|
||||
};
|
12027
community/learn/graphql-tutorials/tutorials/angular-apollo/app-boilerplate/package-lock.json
generated
Normal file
12027
community/learn/graphql-tutorials/tutorials/angular-apollo/app-boilerplate/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "app-boilerplate",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve --port 3000",
|
||||
"build": "ng build",
|
||||
"test": "ng test",
|
||||
"lint": "ng lint",
|
||||
"e2e": "ng e2e"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "~8.1.2",
|
||||
"@angular/common": "~8.1.2",
|
||||
"@angular/compiler": "~8.1.2",
|
||||
"@angular/core": "~8.1.2",
|
||||
"@angular/forms": "~8.1.2",
|
||||
"@angular/platform-browser": "~8.1.2",
|
||||
"@angular/platform-browser-dynamic": "~8.1.2",
|
||||
"@angular/router": "~8.1.2",
|
||||
"auth0-js": "^9.10.0",
|
||||
"rxjs": "~6.4.0",
|
||||
"tslib": "^1.9.0",
|
||||
"zone.js": "~0.9.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "~0.801.2",
|
||||
"@angular/cli": "~8.1.2",
|
||||
"@angular/compiler-cli": "~8.1.2",
|
||||
"@angular/language-service": "~8.1.2",
|
||||
"@types/node": "~8.9.4",
|
||||
"@types/jasmine": "~3.3.8",
|
||||
"@types/jasminewd2": "~2.0.3",
|
||||
"codelyzer": "^5.0.0",
|
||||
"jasmine-core": "~3.4.0",
|
||||
"jasmine-spec-reporter": "~4.2.1",
|
||||
"karma": "~4.1.0",
|
||||
"karma-chrome-launcher": "~2.2.0",
|
||||
"karma-coverage-istanbul-reporter": "~2.0.1",
|
||||
"karma-jasmine": "~2.0.1",
|
||||
"karma-jasmine-html-reporter": "^1.4.0",
|
||||
"protractor": "~5.4.0",
|
||||
"ts-node": "~7.0.0",
|
||||
"tslint": "~5.15.0",
|
||||
"typescript": "~3.4.3"
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
import { Component, Output, EventEmitter } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'App',
|
||||
templateUrl: './app.component.html',
|
||||
})
|
||||
export class App {
|
||||
@Output('logout') logout: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
logoutHandler(item: any) {
|
||||
this.logout.emit(item);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
<Callback *ngIf="isAuthenticated == 'loading'"></Callback>
|
||||
<Login *ngIf="!isAuthenticated" (loginHandler)="login($event)"></Login>
|
||||
|
||||
<App *ngIf="isAuthenticated === true" (logout)="logout()"></App>
|
||||
|
||||
<router-outlet></router-outlet>
|
@ -0,0 +1,125 @@
|
||||
import { Component, OnInit, Input } from '@angular/core';
|
||||
import auth0 from 'auth0-js';
|
||||
|
||||
import {AUTH_CONFIG} from './auth0-variables';
|
||||
|
||||
@Component({
|
||||
selector: 'Auth0Wrapper',
|
||||
templateUrl: './Auth0Wrapper.template.html',
|
||||
})
|
||||
|
||||
export class Auth0Wrapper implements OnInit {
|
||||
auth0 = new auth0.WebAuth({
|
||||
domain: AUTH_CONFIG.domain,
|
||||
clientID: AUTH_CONFIG.clientId,
|
||||
redirectUri: AUTH_CONFIG.callbackUrl,
|
||||
audience: `https://${AUTH_CONFIG.domain}/userinfo`,
|
||||
responseType: 'token id_token',
|
||||
scope: 'openid profile'
|
||||
});
|
||||
isAuthenticated:any = false;// This can be true, false, 'loading'
|
||||
idToken = null;
|
||||
accessToken;
|
||||
expiresAt;
|
||||
@Input('location') location: any;
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
login() {
|
||||
this.auth0.authorize();
|
||||
}
|
||||
|
||||
handleAuthentication = () => {
|
||||
this.isAuthenticated= 'loading';
|
||||
|
||||
this.auth0.parseHash((err, authResult) => {
|
||||
if (authResult && authResult.accessToken && authResult.idToken) {
|
||||
this.setSession(authResult);
|
||||
} else if (err) {
|
||||
this.logout();
|
||||
console.error(err);
|
||||
alert(`Error: ${err.error} - ${err.errorDescription}`);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
getAccessToken() {
|
||||
return this.accessToken;
|
||||
}
|
||||
|
||||
getIdToken() {
|
||||
return this.idToken;
|
||||
}
|
||||
|
||||
setSession(authResult) {
|
||||
// Set isLoggedIn flag in localStorage
|
||||
localStorage.setItem('isLoggedIn', 'true');
|
||||
|
||||
// Set the time that the access token will expire at
|
||||
let expiresAt = (authResult.expiresIn * 1000) + new Date().getTime();
|
||||
this.accessToken = authResult.accessToken;
|
||||
this.idToken = authResult.idToken;
|
||||
this.expiresAt = expiresAt;
|
||||
|
||||
// navigate to the home route
|
||||
this.isAuthenticated = true;
|
||||
this.idToken = authResult.idToken;
|
||||
}
|
||||
|
||||
renewSession() {
|
||||
this.isAuthenticated = 'loading';
|
||||
|
||||
this.auth0.checkSession({}, (err, authResult) => {
|
||||
if (authResult && authResult.accessToken && authResult.idToken) {
|
||||
this.setSession(authResult);
|
||||
} else if (err) {
|
||||
this.logout();
|
||||
console.log(err);
|
||||
alert(`Could not get a new token (${err.error}: ${err.error_description}).`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
logout() {
|
||||
// Remove tokens and expiry time
|
||||
this.accessToken = null;
|
||||
this.idToken = null;
|
||||
this.expiresAt = 0;
|
||||
|
||||
// Remove isLoggedIn flag from localStorage
|
||||
localStorage.removeItem('isLoggedIn');
|
||||
|
||||
this.auth0.logout({
|
||||
return_to: window.location.origin
|
||||
});
|
||||
|
||||
// navigate to the home route
|
||||
this.isAuthenticated = false;
|
||||
this.idToken = null;
|
||||
}
|
||||
|
||||
isExpired () {
|
||||
// Check whether the current time is past the
|
||||
// access token's expiry time
|
||||
let expiresAt = this.expiresAt;
|
||||
return new Date().getTime() > expiresAt;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
// If this is a callback URL then do the right things
|
||||
const location = window.location;
|
||||
if (location && location.pathname.startsWith('/callback') && /access_token|id_token|error/.test(location.hash)) {
|
||||
this.handleAuthentication();
|
||||
return;
|
||||
}
|
||||
|
||||
// On first load, check if we are already logged in and get the idTokens and things
|
||||
if(localStorage.getItem('isLoggedIn') === 'true') {
|
||||
this.renewSession();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
<div class="callback">
|
||||
<img src="../../assets/loading.svg" alt="loading" />
|
||||
</div>
|
@ -0,0 +1,11 @@
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'Callback',
|
||||
templateUrl: './Callback.template.html',
|
||||
})
|
||||
|
||||
export class Callback {
|
||||
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
<div class="overlay">
|
||||
<div class="overlay-content">
|
||||
<div class="overlay-heading">
|
||||
Welcome to the GraphQL tutorial app
|
||||
</div>
|
||||
<div class="overlay-message">
|
||||
Please login to continue
|
||||
</div>
|
||||
<div class="overlay-action">
|
||||
<Button
|
||||
id="qsLoginBtn"
|
||||
type="button"
|
||||
class="btn-margin loginBtn btn btn-primary "
|
||||
(click)="loginHandlerWrapper()">
|
||||
Log In
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,15 @@
|
||||
import { Component, Output, EventEmitter } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'Login',
|
||||
templateUrl: './Login.template.html',
|
||||
})
|
||||
|
||||
export class Login {
|
||||
@Output('loginHandler') loginHandler: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
loginHandlerWrapper(item: any) {
|
||||
this.loginHandler.emit(item);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,8 @@
|
||||
<Button
|
||||
id="qsLogoutBtn"
|
||||
type="button"
|
||||
class="btn-margin logoutBtn btn btn-primary "
|
||||
(click)="logoutHandlerWrapper()"
|
||||
>
|
||||
Log Out
|
||||
</Button>
|
@ -0,0 +1,14 @@
|
||||
import { Component, EventEmitter, Output } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'LogoutBtn',
|
||||
templateUrl: './Logout.template.html',
|
||||
})
|
||||
|
||||
export class LogoutBtn {
|
||||
@Output('logoutHandler') logoutHandler: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
logoutHandlerWrapper(item: any) {
|
||||
this.logoutHandler.emit(item);
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
export const AUTH_CONFIG = {
|
||||
domain: 'graphql-tutorials.auth0.com',
|
||||
clientId: 'P38qnFo1lFAQJrzkun--wEzqljVNGcWW',
|
||||
callbackUrl: 'http://localhost:3000/callback'
|
||||
};
|
||||
|
@ -0,0 +1,14 @@
|
||||
<nav class="m-bottom-0 navbar navbar-default">
|
||||
<div class="container-fluid">
|
||||
<div class="navHeader navbar-header">
|
||||
<span class="navBrand navbar-brand">GraphQL Tutorial App</span>
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
<li role="presentation" class="">
|
||||
<a role="button" href="#">
|
||||
<LogoutBtn (logoutHandler)="logoutHandlerWrapper()"></LogoutBtn>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
@ -0,0 +1,15 @@
|
||||
|
||||
import { Component, EventEmitter, Output } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'Header',
|
||||
templateUrl: './Header.template.html',
|
||||
})
|
||||
|
||||
export class Header {
|
||||
@Output('logoutHandler') logoutHandler: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
logoutHandlerWrapper(item: any) {
|
||||
this.logoutHandler.emit(item);
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
<div class="userInfo">
|
||||
<div class="userImg">
|
||||
<i class="far fa-user" ></i>
|
||||
</div>
|
||||
<div class="userName">
|
||||
{{user.name}}
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,10 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'OnlineUser',
|
||||
templateUrl: './OnlineUser.template.html',
|
||||
})
|
||||
|
||||
export class OnlineUser {
|
||||
@Input('user') user: any;
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
<div class="onlineUsersWrapper">
|
||||
<div class="sliderHeader">
|
||||
Online users - {{onlineUsers.length}}
|
||||
</div>
|
||||
<ng-container *ngFor="let user of onlineUsers; let i = index" [attr.data-index]="i">
|
||||
<OnlineUser [user]="user"></OnlineUser>
|
||||
</ng-container>
|
||||
</div>
|
@ -0,0 +1,14 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'OnlineUsersWrapper',
|
||||
templateUrl: './OnlineUsersWrapper.template.html',
|
||||
})
|
||||
|
||||
export class OnlineUsersWrapper {
|
||||
onlineUsers = [
|
||||
{ name: "someUser1" },
|
||||
{ name: "someUser2" }
|
||||
]
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
<li>
|
||||
<div class="userInfoPublic" title={todo.user.name}>
|
||||
@{{todo.user.name}}
|
||||
</div>
|
||||
|
||||
<div [ngClass]="todo.is_completed ? 'labelContent completed' : 'labelContent'">
|
||||
<div>
|
||||
{{todo.title}}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
@ -0,0 +1,10 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'TaskItem',
|
||||
templateUrl: './TaskItem.template.html',
|
||||
})
|
||||
|
||||
export class TaskItem {
|
||||
@Input('todo') todo: any;
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
<div class="footerList">
|
||||
<span> {{itemCount}} item{{itemCount !== 1 ? "s" : ""}}</span>
|
||||
|
||||
<ul>
|
||||
<li (click)="filterResultsHandler('all')">
|
||||
<a [ngClass]="currentFilter === 'all' ? 'selected' : ''">
|
||||
All
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li (click)="filterResultsHandler('active')">
|
||||
<a [ngClass]= "currentFilter === 'active' ? 'selected' : ''">
|
||||
Active
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li (click)="filterResultsHandler('completed')">
|
||||
<a [ngClass]="currentFilter === 'completed' ? 'selected' : ''">
|
||||
Completed
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<button (click)="clearCompletedFnWrapper()" class="clearComp">
|
||||
Clear completed
|
||||
</button>
|
||||
</div>
|
@ -0,0 +1,38 @@
|
||||
import { Component, Input, Output, EventEmitter, OnChanges } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'TodoFilters',
|
||||
templateUrl: './TodoFilters.template.html',
|
||||
})
|
||||
|
||||
export class TodoFilters implements OnChanges{
|
||||
@Input('todos') todos: any;
|
||||
@Input('currentFilter') currentFilter: any;
|
||||
@Output('filterResultsFn') filterResultsFn: EventEmitter<any> = new EventEmitter();;
|
||||
@Output('clearCompletedFn') clearCompletedFn: EventEmitter<any> = new EventEmitter();;
|
||||
|
||||
itemCount: any;
|
||||
|
||||
ngOnChanges() {
|
||||
this.updateItemCount();
|
||||
}
|
||||
|
||||
updateItemCount() {
|
||||
this.itemCount = this.todos.length;
|
||||
const activeTodos = this.todos.filter(todo => todo.is_completed !== true);
|
||||
|
||||
if (this.currentFilter === 'active') {
|
||||
this.itemCount = activeTodos.length;
|
||||
} else if (this.currentFilter === 'completed') {
|
||||
this.itemCount = this.todos.length - activeTodos.length;
|
||||
}
|
||||
}
|
||||
|
||||
clearCompletedFnWrapper(item: any) {
|
||||
this.clearCompletedFn.emit(item);
|
||||
}
|
||||
|
||||
filterResultsHandler(filter) {
|
||||
this.filterResultsFn.emit({ event:event, filter: filter });
|
||||
};
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
<form class="formInput" (submit)="addTodo($event)">
|
||||
<input
|
||||
class="input"
|
||||
placeholder="What needs to be done?"
|
||||
/>
|
||||
<i class="inputMarker fa fa-angle-right"></i>
|
||||
</form>
|
@ -0,0 +1,14 @@
|
||||
import { Component, OnInit, Input } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'TodoInput',
|
||||
templateUrl: './TodoInput.template.html'
|
||||
})
|
||||
|
||||
export class TodoInput {
|
||||
@Input('isPublic') isPublic: any = false;
|
||||
|
||||
addTodo(e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
<li>
|
||||
<div class="view">
|
||||
<div class="round">
|
||||
<input
|
||||
[checked]="todo.is_completed"
|
||||
type="checkbox"
|
||||
id={{todo.id}}
|
||||
disabled
|
||||
(change)="toggleTodo()"
|
||||
/>
|
||||
<label htmlFor={{todo.id}}> </label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div [ngClass]="todo.is_completed ? 'labelContent completed' : 'labelContent'">
|
||||
<div>
|
||||
{{todo.title}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="closeBtn" (click)="removeTodo($event)">
|
||||
x
|
||||
</button>
|
||||
</li>
|
@ -0,0 +1,17 @@
|
||||
import { Component, OnInit, Input } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'TodoItem',
|
||||
templateUrl: './TodoItem.template.html',
|
||||
})
|
||||
|
||||
export class TodoItem {
|
||||
@Input('todo') todo: any;
|
||||
|
||||
removeTodo = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
toggleTodo = () => {};
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
<div class="todoListWrapper">
|
||||
<ul>
|
||||
<ng-container *ngFor="let todo of filteredTodos; let i = index" [attr.data-index]="i">
|
||||
<TodoItem
|
||||
[todo]="todo"
|
||||
></TodoItem>
|
||||
</ng-container>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<TodoFilters
|
||||
[todos]="filteredTodos"
|
||||
[currentFilter]="filter"
|
||||
(filterResultsFn)="filterResults($event)"
|
||||
(clearCompletedFn)="clearCompleted()"
|
||||
></TodoFilters>
|
@ -0,0 +1,44 @@
|
||||
import { Component, OnInit, Input } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'TodoPrivateList',
|
||||
templateUrl: './TodoPrivateList.template.html',
|
||||
})
|
||||
|
||||
export class TodoPrivateList implements OnInit {
|
||||
|
||||
filter = "all";
|
||||
clearInProgress= false;
|
||||
todos= [
|
||||
{
|
||||
id: "1",
|
||||
title: "This is private todo 1",
|
||||
is_completed: true,
|
||||
is_public: false
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
title: "This is private todo 2",
|
||||
is_completed: false,
|
||||
is_public: false
|
||||
}
|
||||
];
|
||||
filteredTodos: any;
|
||||
|
||||
ngOnInit() {
|
||||
this.filteredTodos = this.todos;
|
||||
|
||||
}
|
||||
|
||||
filterResults($event) {
|
||||
this.filter = $event.filter;
|
||||
this.filteredTodos = this.todos;
|
||||
if (this.filter === "active") {
|
||||
this.filteredTodos = this.todos.filter(todo => todo.is_completed !== true);
|
||||
} else if (this.filter === "completed") {
|
||||
this.filteredTodos = this.todos.filter(todo => todo.is_completed === true);
|
||||
}
|
||||
}
|
||||
|
||||
clearCompleted() {}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
<div class="todoWrapper">
|
||||
<div class="sectionHeader">Personal todos</div>
|
||||
<TodoInput></TodoInput>
|
||||
<TodoPrivateList></TodoPrivateList>
|
||||
</div>
|
@ -0,0 +1,9 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'TodoPrivateWrapper',
|
||||
templateUrl: './TodoPrivateWrapper.template.html',
|
||||
})
|
||||
|
||||
export class TodoPrivateWrapper {
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
<div class="todoListWrapper">
|
||||
<div *ngIf="newTodosCount" class="loadMoreSection" (click)="loadNew()">
|
||||
New tasks have arrived! ({{newTodosCount}})
|
||||
</div>
|
||||
|
||||
<ul>
|
||||
<ng-container *ngFor="let todo of todos; let i = index" [attr.data-index]="i">
|
||||
<TaskItem
|
||||
[todo]="todo"
|
||||
></TaskItem>
|
||||
</ng-container>
|
||||
</ul>
|
||||
|
||||
<div class="loadMoreSection" (click)="loadOlder()">
|
||||
{{olderTodosAvailable ? 'Load older tasks' : 'No more public tasks!'}}
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,48 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'TodoPublicList',
|
||||
templateUrl: './TodoPublicList.template.html',
|
||||
})
|
||||
|
||||
export class TodoPublicList {
|
||||
olderTodosAvailable= true;
|
||||
newTodosCount= 1;
|
||||
todos= [
|
||||
{
|
||||
id: "1",
|
||||
title: "This is public todo 1",
|
||||
user: {
|
||||
name: "someUser1"
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
title: "This is public todo 2",
|
||||
is_completed: false,
|
||||
is_public: true,
|
||||
user: {
|
||||
name: "someUser2"
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
title: "This is public todo 3",
|
||||
user: {
|
||||
name: "someUser3"
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
title: "This is public todo 4",
|
||||
user: {
|
||||
name: "someUser4"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
loadNew() {}
|
||||
|
||||
loadOlder() {}
|
||||
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
<div class="todoWrapper">
|
||||
<div class="sectionHeader">Public feed (realtime)</div>
|
||||
|
||||
<TodoInput isPublic ></TodoInput>
|
||||
<TodoPublicList></TodoPublicList>
|
||||
</div>
|
@ -0,0 +1,9 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'TodoPublicWrapper',
|
||||
templateUrl: './TodoPublicWrapper.template.html',
|
||||
})
|
||||
|
||||
export class TodoPublicWrapper {
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
|
||||
|
||||
const routes: Routes = [];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forRoot(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class AppRoutingModule { }
|
@ -0,0 +1,18 @@
|
||||
<div>
|
||||
<Header (logoutHandler)="logoutHandler()"></Header>
|
||||
<div class="container-fluid p-left-right-0">
|
||||
<div class="col-xs-12 col-md-9 p-left-right-0">
|
||||
<div class="col-xs-12 col-md-6 sliderMenu p-30">
|
||||
<TodoPrivateWrapper></TodoPrivateWrapper>
|
||||
</div>
|
||||
<div class="col-xs-12 col-md-6 sliderMenu p-30 bg-gray border-right">
|
||||
<TodoPublicWrapper ></TodoPublicWrapper>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-md-3 p-left-right-0">
|
||||
<div class="col-xs-12 col-md-12 sliderMenu p-30 bg-gray">
|
||||
<OnlineUsersWrapper ></OnlineUsersWrapper>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,35 @@
|
||||
import { TestBed, async } from '@angular/core/testing';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { App } from './App';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
RouterTestingModule
|
||||
],
|
||||
declarations: [
|
||||
AppComponent
|
||||
],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
it('should create the app', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should have as title 'app-boilerplate'`, () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app.title).toEqual('app-boilerplate');
|
||||
});
|
||||
|
||||
it('should render title in a h1 tag', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.debugElement.nativeElement;
|
||||
expect(compiled.querySelector('h1').textContent).toContain('Welcome to app-boilerplate!');
|
||||
});
|
||||
});
|
@ -0,0 +1,48 @@
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { App } from './App';
|
||||
import { Auth0Wrapper } from './Auth/Auth0Wrapper';
|
||||
import { Callback } from './Auth/Callback';
|
||||
import { Login } from './Auth/Login';
|
||||
import { LogoutBtn } from './Auth/LogoutBtn';
|
||||
import { OnlineUser } from './OnlineUsers/OnlineUser';
|
||||
import { OnlineUsersWrapper } from './OnlineUsers/OnlineUsersWrapper';
|
||||
import { TaskItem } from './Todo/TaskItem';
|
||||
import { TodoFilters } from './Todo/TodoFilters';
|
||||
import { TodoInput } from './Todo/TodoInput';
|
||||
import { TodoItem } from './Todo/TodoItem';
|
||||
import { TodoPrivateList } from './Todo/TodoPrivateList';
|
||||
import { TodoPrivateWrapper } from './Todo/TodoPrivateWrapper';
|
||||
import { TodoPublicList } from './Todo/TodoPublicList';
|
||||
import { TodoPublicWrapper } from './Todo/TodoPublicWrapper';
|
||||
import { Header} from './Header';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
Auth0Wrapper,
|
||||
Callback,
|
||||
Login,
|
||||
LogoutBtn,
|
||||
OnlineUser,
|
||||
OnlineUsersWrapper,
|
||||
TaskItem,
|
||||
TodoFilters,
|
||||
TodoInput,
|
||||
TodoItem,
|
||||
TodoPrivateList,
|
||||
TodoPrivateWrapper,
|
||||
TodoPublicList,
|
||||
TodoPublicWrapper,
|
||||
App,
|
||||
Header
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
AppRoutingModule
|
||||
],
|
||||
providers: [],
|
||||
bootstrap: [Auth0Wrapper]
|
||||
})
|
||||
export class AppModule { }
|
@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><svg width='120px' height='120px' xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" class="uil-ring"><rect x="0" y="0" width="100" height="100" fill="none" class="bk"></rect><defs><filter id="uil-ring-shadow" x="-100%" y="-100%" width="300%" height="300%"><feOffset result="offOut" in="SourceGraphic" dx="0" dy="0"></feOffset><feGaussianBlur result="blurOut" in="offOut" stdDeviation="0"></feGaussianBlur><feBlend in="SourceGraphic" in2="blurOut" mode="normal"></feBlend></filter></defs><path d="M10,50c0,0,0,0.5,0.1,1.4c0,0.5,0.1,1,0.2,1.7c0,0.3,0.1,0.7,0.1,1.1c0.1,0.4,0.1,0.8,0.2,1.2c0.2,0.8,0.3,1.8,0.5,2.8 c0.3,1,0.6,2.1,0.9,3.2c0.3,1.1,0.9,2.3,1.4,3.5c0.5,1.2,1.2,2.4,1.8,3.7c0.3,0.6,0.8,1.2,1.2,1.9c0.4,0.6,0.8,1.3,1.3,1.9 c1,1.2,1.9,2.6,3.1,3.7c2.2,2.5,5,4.7,7.9,6.7c3,2,6.5,3.4,10.1,4.6c3.6,1.1,7.5,1.5,11.2,1.6c4-0.1,7.7-0.6,11.3-1.6 c3.6-1.2,7-2.6,10-4.6c3-2,5.8-4.2,7.9-6.7c1.2-1.2,2.1-2.5,3.1-3.7c0.5-0.6,0.9-1.3,1.3-1.9c0.4-0.6,0.8-1.3,1.2-1.9 c0.6-1.3,1.3-2.5,1.8-3.7c0.5-1.2,1-2.4,1.4-3.5c0.3-1.1,0.6-2.2,0.9-3.2c0.2-1,0.4-1.9,0.5-2.8c0.1-0.4,0.1-0.8,0.2-1.2 c0-0.4,0.1-0.7,0.1-1.1c0.1-0.7,0.1-1.2,0.2-1.7C90,50.5,90,50,90,50s0,0.5,0,1.4c0,0.5,0,1,0,1.7c0,0.3,0,0.7,0,1.1 c0,0.4-0.1,0.8-0.1,1.2c-0.1,0.9-0.2,1.8-0.4,2.8c-0.2,1-0.5,2.1-0.7,3.3c-0.3,1.2-0.8,2.4-1.2,3.7c-0.2,0.7-0.5,1.3-0.8,1.9 c-0.3,0.7-0.6,1.3-0.9,2c-0.3,0.7-0.7,1.3-1.1,2c-0.4,0.7-0.7,1.4-1.2,2c-1,1.3-1.9,2.7-3.1,4c-2.2,2.7-5,5-8.1,7.1 c-0.8,0.5-1.6,1-2.4,1.5c-0.8,0.5-1.7,0.9-2.6,1.3L66,87.7l-1.4,0.5c-0.9,0.3-1.8,0.7-2.8,1c-3.8,1.1-7.9,1.7-11.8,1.8L47,90.8 c-1,0-2-0.2-3-0.3l-1.5-0.2l-0.7-0.1L41.1,90c-1-0.3-1.9-0.5-2.9-0.7c-0.9-0.3-1.9-0.7-2.8-1L34,87.7l-1.3-0.6 c-0.9-0.4-1.8-0.8-2.6-1.3c-0.8-0.5-1.6-1-2.4-1.5c-3.1-2.1-5.9-4.5-8.1-7.1c-1.2-1.2-2.1-2.7-3.1-4c-0.5-0.6-0.8-1.4-1.2-2 c-0.4-0.7-0.8-1.3-1.1-2c-0.3-0.7-0.6-1.3-0.9-2c-0.3-0.7-0.6-1.3-0.8-1.9c-0.4-1.3-0.9-2.5-1.2-3.7c-0.3-1.2-0.5-2.3-0.7-3.3 c-0.2-1-0.3-2-0.4-2.8c-0.1-0.4-0.1-0.8-0.1-1.2c0-0.4,0-0.7,0-1.1c0-0.7,0-1.2,0-1.7C10,50.5,10,50,10,50z" fill="#337ab7" filter="url(#uil-ring-shadow)"><animateTransform attributeName="transform" type="rotate" from="0 50 50" to="360 50 50" repeatCount="indefinite" dur="1s"></animateTransform></path></svg>
|
After Width: | Height: | Size: 2.2 KiB |
@ -0,0 +1,3 @@
|
||||
export const environment = {
|
||||
production: true
|
||||
};
|
@ -0,0 +1,16 @@
|
||||
// This file can be replaced during build by using the `fileReplacements` array.
|
||||
// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
|
||||
// The list of file replacements can be found in `angular.json`.
|
||||
|
||||
export const environment = {
|
||||
production: false
|
||||
};
|
||||
|
||||
/*
|
||||
* For easier debugging in development mode, you can import the following file
|
||||
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
|
||||
*
|
||||
* This import should be commented out in production mode because it will have a negative impact
|
||||
* on performance if an error is thrown.
|
||||
*/
|
||||
// import 'zone.js/dist/zone-error'; // Included with Angular CLI.
|
Binary file not shown.
After Width: | Height: | Size: 5.3 KiB |
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 26 26" enable-background="new 0 0 26 26" width="512px" height="512px">
|
||||
<path d="m.3,14c-0.2-0.2-0.3-0.5-0.3-0.7s0.1-0.5 0.3-0.7l1.4-1.4c0.4-0.4 1-0.4 1.4,0l.1,.1 5.5,5.9c0.2,0.2 0.5,0.2 0.7,0l13.4-13.9h0.1v-8.88178e-16c0.4-0.4 1-0.4 1.4,0l1.4,1.4c0.4,0.4 0.4,1 0,1.4l0,0-16,16.6c-0.2,0.2-0.4,0.3-0.7,0.3-0.3,0-0.5-0.1-0.7-0.3l-7.8-8.4-.2-.3z" fill="#91DC5A"/>
|
||||
</svg>
|
After Width: | Height: | Size: 523 B |
@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
|
||||
<g fill="#61DAFB">
|
||||
<path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
|
||||
<circle cx="420.9" cy="296.5" r="45.7"/>
|
||||
<path d="M520.5 78.1z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
@ -0,0 +1,17 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>GraphQL Angular Tutorial</title>
|
||||
<base href="/">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
|
||||
<link href="https://afeld.github.io/emoji-css/emoji.css" rel="stylesheet">
|
||||
<link href="https://use.fontawesome.com/releases/v5.0.7/css/all.css" rel="stylesheet" crossorigin="anonymous">
|
||||
</head>
|
||||
<body>
|
||||
<Auth0Wrapper></Auth0Wrapper>
|
||||
</body>
|
||||
</html>
|
@ -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.error(err));
|
@ -0,0 +1,63 @@
|
||||
/**
|
||||
* 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/guide/browser-support
|
||||
*/
|
||||
|
||||
/***************************************************************************************************
|
||||
* BROWSER POLYFILLS
|
||||
*/
|
||||
|
||||
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
|
||||
// import 'classlist.js'; // Run `npm install --save classlist.js`.
|
||||
|
||||
/**
|
||||
* Web Animations `@angular/platform-browser/animations`
|
||||
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
|
||||
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
|
||||
*/
|
||||
// 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
|
||||
* because those flags need to be set before `zone.js` being loaded, and webpack
|
||||
* will put import in the top of bundle, so user need to create a separate file
|
||||
* in this directory (for example: zone-flags.ts), and put the following flags
|
||||
* into that file, and then add the following code before importing zone.js.
|
||||
* import './zone-flags.ts';
|
||||
*
|
||||
* The flags allowed in zone-flags.ts are listed here.
|
||||
*
|
||||
* The following flags will work for all browsers.
|
||||
*
|
||||
* (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__UNPATCHED_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
|
||||
*/
|
@ -0,0 +1,414 @@
|
||||
/* You can add global styles to this file, and also import other style files */
|
||||
@import url("https://fonts.googleapis.com/css?family=Open+Sans:400,600,700");
|
||||
|
||||
body {
|
||||
background-color: #f7f7f7;
|
||||
font-weight: 400;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.wd-95 {
|
||||
width: 95%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.p-left-0 {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.p-left-right-0 {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.p-30 {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.p-top-bottom-30 {
|
||||
padding-top: 30px;
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
|
||||
.border-right {
|
||||
border-right: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.bg-gray {
|
||||
background-color: #efeded;
|
||||
}
|
||||
|
||||
.display-block {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.m-bottom-0 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.m-bottom-10 {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.m-bottom-20 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.navHeader {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.navHeader button {
|
||||
padding: 3.75px 7.5px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.breakWord {
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.navBrand {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.todoWrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sectionHeader {
|
||||
font-size: 18px;
|
||||
font-weight: 400;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.formInput input {
|
||||
height: 60px;
|
||||
padding: 16px 16px 16px 60px;
|
||||
border: none;
|
||||
background-color: #fff;
|
||||
width: 100%;
|
||||
-webkit-box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.1);
|
||||
-moz-box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.1);
|
||||
opacity: 0.6;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
|
||||
.formInput {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.inputMarker {
|
||||
position: absolute;
|
||||
left: 15px;
|
||||
font-size: 25px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.todoListWrapper {
|
||||
border-top: 1px solid #e6e6e6;
|
||||
-webkit-box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.1);
|
||||
-moz-box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.todoListWrapper ul {
|
||||
-webkit-padding-start: 0px;
|
||||
-moz-padding-start: 0px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.todoListWrapper ul li {
|
||||
list-style-type: none;
|
||||
min-height: 60px;
|
||||
max-height: 60px;
|
||||
/* overflow: auto; */
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #ededed;
|
||||
background-color: #fff;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.closeBtn {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
color: #cc9a9a;
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
padding: 0;
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
.closeBtn i {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.closeBtn:hover {
|
||||
color: #af5b5e;
|
||||
}
|
||||
|
||||
.closeBtn:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.labelContent {
|
||||
padding-left: 1px;
|
||||
color: #777;
|
||||
width: calc(100% - 128px);
|
||||
max-height: 60px;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.labelContent.completed {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.view {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
margin: 0 15px;
|
||||
}
|
||||
|
||||
.footerList {
|
||||
height: 60px;
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #ededed;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 15px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.footerList:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 50px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2), 0 8px 0 -3px #f6f6f6,
|
||||
0 9px 1px -3px rgba(0, 0, 0, 0.2), 0 16px 0 -6px #f6f6f6,
|
||||
0 17px 2px -6px rgba(0, 0, 0, 0.2);
|
||||
-moz-box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2), 0 8px 0 -3px #f6f6f6,
|
||||
0 9px 1px -3px rgba(0, 0, 0, 0.2), 0 16px 0 -6px #f6f6f6,
|
||||
0 17px 2px -6px rgba(0, 0, 0, 0.2);
|
||||
-webkit-box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2), 0 8px 0 -3px #f6f6f6,
|
||||
0 9px 1px -3px rgba(0, 0, 0, 0.2), 0 16px 0 -6px #f6f6f6,
|
||||
0 17px 2px -6px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.footerList ul {
|
||||
-webkit-padding-start: 0px;
|
||||
-moz-padding-start: 0px;
|
||||
margin-bottom: 0;
|
||||
padding-left: 20px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.footerList ul li {
|
||||
list-style-type: none;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.footerList ul li a {
|
||||
padding: 3px 7px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 3px;
|
||||
color: #777;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.footerList ul li a.selected {
|
||||
border-color: rgba(175, 47, 47, 0.2);
|
||||
}
|
||||
|
||||
.clearComp {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
color: #777;
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.clearComp:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.sliderMenu {
|
||||
height: calc(100vh - 50px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.sliderHeader {
|
||||
padding-bottom: 20px;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.userInfo {
|
||||
padding: 10px 0 10px 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.userInfo:hover {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
.userImg {
|
||||
text-align: center;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.userImg i {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.userImg img {
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
/*Custom Checkbox */
|
||||
.round {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.round label {
|
||||
background-color: #fff;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
height: 28px;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 28px;
|
||||
min-width: 28px;
|
||||
}
|
||||
|
||||
.round label:after {
|
||||
border: 2px solid #66bb6a;
|
||||
border-top: none;
|
||||
border-right: none;
|
||||
content: "";
|
||||
height: 6px;
|
||||
left: 7px;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
transform: rotate(-45deg);
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
.round input[type="checkbox"] {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.round input[type="checkbox"]:checked + label {
|
||||
background-color: #fff;
|
||||
border-color: #66bb6a;
|
||||
}
|
||||
|
||||
.round input[type="checkbox"]:checked + label:after {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.loadMoreSection {
|
||||
list-style-type: none;
|
||||
height: 30px;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #ededed;
|
||||
background-color: #e6ecf0;
|
||||
position: relative;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.userInfoPublic {
|
||||
background-color: #dfe3e6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: 10px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
padding: 5px 8px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
position: fixed;
|
||||
display: block;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #ddd;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.overlay .overlay-content {
|
||||
position: absolute;
|
||||
top: 40%;
|
||||
left: 50%;
|
||||
font-size: 18px;
|
||||
text-align: center;
|
||||
transform: translate(-50%,-50%);
|
||||
-ms-transform: translate(-50%,-50%);
|
||||
}
|
||||
|
||||
.overlay .overlay-heading {
|
||||
font-size: 24px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 20px;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
.overlay .overlay-message {
|
||||
color: #5f5f5f;
|
||||
}
|
||||
|
||||
.overlay .overlay-action {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
.sliderMenu {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.labelContent {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.callback {
|
||||
position: "absolute";
|
||||
display: "flex";
|
||||
justify-content: "center";
|
||||
height: "100vh";
|
||||
width: "100vw";
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: "white"
|
||||
}
|
||||
|
@ -0,0 +1,399 @@
|
||||
@import url("https://fonts.googleapis.com/css?family=Open+Sans:400,600,700");
|
||||
|
||||
body {
|
||||
background-color: #f7f7f7;
|
||||
font-weight: 400;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.wd-95 {
|
||||
width: 95%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.p-left-0 {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.p-left-right-0 {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.p-30 {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.p-top-bottom-30 {
|
||||
padding-top: 30px;
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
|
||||
.border-right {
|
||||
border-right: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.bg-gray {
|
||||
background-color: #efeded;
|
||||
}
|
||||
|
||||
.display-block {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.m-bottom-0 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.m-bottom-10 {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.m-bottom-20 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.navHeader {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.navHeader button {
|
||||
padding: 3.75px 7.5px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.breakWord {
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.navBrand {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.todoWrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sectionHeader {
|
||||
font-size: 18px;
|
||||
font-weight: 400;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.formInput input {
|
||||
height: 60px;
|
||||
padding: 16px 16px 16px 60px;
|
||||
border: none;
|
||||
background-color: #fff;
|
||||
width: 100%;
|
||||
-webkit-box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.1);
|
||||
-moz-box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.1);
|
||||
opacity: 0.6;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
|
||||
.formInput {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.inputMarker {
|
||||
position: absolute;
|
||||
left: 15px;
|
||||
font-size: 25px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.todoListWrapper {
|
||||
border-top: 1px solid #e6e6e6;
|
||||
-webkit-box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.1);
|
||||
-moz-box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.todoListWrapper ul {
|
||||
-webkit-padding-start: 0px;
|
||||
-moz-padding-start: 0px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.todoListWrapper ul li {
|
||||
list-style-type: none;
|
||||
min-height: 60px;
|
||||
max-height: 60px;
|
||||
/* overflow: auto; */
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #ededed;
|
||||
background-color: #fff;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.closeBtn {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
color: #cc9a9a;
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
padding: 0;
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
.closeBtn i {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.closeBtn:hover {
|
||||
color: #af5b5e;
|
||||
}
|
||||
|
||||
.closeBtn:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.labelContent {
|
||||
padding-left: 1px;
|
||||
color: #777;
|
||||
width: calc(100% - 128px);
|
||||
max-height: 60px;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.labelContent.completed {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.view {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
margin: 0 15px;
|
||||
}
|
||||
|
||||
.footerList {
|
||||
height: 60px;
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #ededed;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 15px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.footerList:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 50px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2), 0 8px 0 -3px #f6f6f6,
|
||||
0 9px 1px -3px rgba(0, 0, 0, 0.2), 0 16px 0 -6px #f6f6f6,
|
||||
0 17px 2px -6px rgba(0, 0, 0, 0.2);
|
||||
-moz-box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2), 0 8px 0 -3px #f6f6f6,
|
||||
0 9px 1px -3px rgba(0, 0, 0, 0.2), 0 16px 0 -6px #f6f6f6,
|
||||
0 17px 2px -6px rgba(0, 0, 0, 0.2);
|
||||
-webkit-box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2), 0 8px 0 -3px #f6f6f6,
|
||||
0 9px 1px -3px rgba(0, 0, 0, 0.2), 0 16px 0 -6px #f6f6f6,
|
||||
0 17px 2px -6px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.footerList ul {
|
||||
-webkit-padding-start: 0px;
|
||||
-moz-padding-start: 0px;
|
||||
margin-bottom: 0;
|
||||
padding-left: 20px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.footerList ul li {
|
||||
list-style-type: none;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.footerList ul li a {
|
||||
padding: 3px 7px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 3px;
|
||||
color: #777;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.footerList ul li a.selected {
|
||||
border-color: rgba(175, 47, 47, 0.2);
|
||||
}
|
||||
|
||||
.clearComp {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
color: #777;
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.clearComp:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.sliderMenu {
|
||||
height: calc(100vh - 50px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.sliderHeader {
|
||||
padding-bottom: 20px;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.userInfo {
|
||||
padding: 10px 0 10px 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.userInfo:hover {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
.userImg {
|
||||
text-align: center;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.userImg i {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.userImg img {
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
/*Custom Checkbox */
|
||||
.round {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.round label {
|
||||
background-color: #fff;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
height: 28px;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 28px;
|
||||
min-width: 28px;
|
||||
}
|
||||
|
||||
.round label:after {
|
||||
border: 2px solid #66bb6a;
|
||||
border-top: none;
|
||||
border-right: none;
|
||||
content: "";
|
||||
height: 6px;
|
||||
left: 7px;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
transform: rotate(-45deg);
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
.round input[type="checkbox"] {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.round input[type="checkbox"]:checked + label {
|
||||
background-color: #fff;
|
||||
border-color: #66bb6a;
|
||||
}
|
||||
|
||||
.round input[type="checkbox"]:checked + label:after {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.loadMoreSection {
|
||||
list-style-type: none;
|
||||
height: 30px;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #ededed;
|
||||
background-color: #e6ecf0;
|
||||
position: relative;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.userInfoPublic {
|
||||
background-color: #dfe3e6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: 10px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
padding: 5px 8px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
position: fixed;
|
||||
display: block;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #ddd;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.overlay .overlay-content {
|
||||
position: absolute;
|
||||
top: 40%;
|
||||
left: 50%;
|
||||
font-size: 18px;
|
||||
text-align: center;
|
||||
transform: translate(-50%,-50%);
|
||||
-ms-transform: translate(-50%,-50%);
|
||||
}
|
||||
|
||||
.overlay .overlay-heading {
|
||||
font-size: 24px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 20px;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
.overlay .overlay-message {
|
||||
color: #5f5f5f;
|
||||
}
|
||||
|
||||
.overlay .overlay-action {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
.sliderMenu {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.labelContent {
|
||||
display: block;
|
||||
}
|
||||
}
|
@ -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);
|
@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/app",
|
||||
"types": []
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"src/test.ts",
|
||||
"src/**/*.spec.ts"
|
||||
]
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"outDir": "./dist/out-tsc",
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"downlevelIteration": true,
|
||||
"experimentalDecorators": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"target": "es2015",
|
||||
"typeRoots": [
|
||||
"node_modules/@types"
|
||||
],
|
||||
"lib": [
|
||||
"es2018",
|
||||
"dom"
|
||||
]
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"fullTemplateTypeCheck": true,
|
||||
"strictInjectionParameters": true
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/spec",
|
||||
"types": [
|
||||
"jasmine",
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"src/test.ts",
|
||||
"src/polyfills.ts"
|
||||
],
|
||||
"include": [
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
{
|
||||
"extends": "tslint:recommended",
|
||||
"rules": {
|
||||
"array-type": false,
|
||||
"arrow-parens": false,
|
||||
"deprecation": {
|
||||
"severity": "warning"
|
||||
},
|
||||
"component-class-suffix": true,
|
||||
"contextual-lifecycle": true,
|
||||
"directive-class-suffix": true,
|
||||
"directive-selector": [
|
||||
true,
|
||||
"attribute",
|
||||
"app",
|
||||
"camelCase"
|
||||
],
|
||||
"component-selector": [
|
||||
true,
|
||||
"element",
|
||||
"app",
|
||||
"kebab-case"
|
||||
],
|
||||
"import-blacklist": [
|
||||
true,
|
||||
"rxjs/Rx"
|
||||
],
|
||||
"interface-name": false,
|
||||
"max-classes-per-file": false,
|
||||
"max-line-length": [
|
||||
true,
|
||||
140
|
||||
],
|
||||
"member-access": false,
|
||||
"member-ordering": [
|
||||
true,
|
||||
{
|
||||
"order": [
|
||||
"static-field",
|
||||
"instance-field",
|
||||
"static-method",
|
||||
"instance-method"
|
||||
]
|
||||
}
|
||||
],
|
||||
"no-consecutive-blank-lines": false,
|
||||
"no-console": [
|
||||
true,
|
||||
"debug",
|
||||
"info",
|
||||
"time",
|
||||
"timeEnd",
|
||||
"trace"
|
||||
],
|
||||
"no-empty": false,
|
||||
"no-inferrable-types": [
|
||||
true,
|
||||
"ignore-params"
|
||||
],
|
||||
"no-non-null-assertion": true,
|
||||
"no-redundant-jsdoc": true,
|
||||
"no-switch-case-fall-through": true,
|
||||
"no-use-before-declare": true,
|
||||
"no-var-requires": false,
|
||||
"object-literal-key-quotes": [
|
||||
true,
|
||||
"as-needed"
|
||||
],
|
||||
"object-literal-sort-keys": false,
|
||||
"ordered-imports": false,
|
||||
"quotemark": [
|
||||
true,
|
||||
"single"
|
||||
],
|
||||
"trailing-comma": false,
|
||||
"no-conflicting-lifecycle": true,
|
||||
"no-host-metadata-property": true,
|
||||
"no-input-rename": true,
|
||||
"no-inputs-metadata-property": true,
|
||||
"no-output-native": true,
|
||||
"no-output-on-prefix": true,
|
||||
"no-output-rename": true,
|
||||
"no-outputs-metadata-property": true,
|
||||
"template-banana-in-box": true,
|
||||
"template-no-negated-async": true,
|
||||
"use-lifecycle-interface": true,
|
||||
"use-pipe-transform-interface": true
|
||||
},
|
||||
"rulesDirectory": [
|
||||
"codelyzer"
|
||||
]
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
# Editor configuration, see https://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
|
46
community/learn/graphql-tutorials/tutorials/angular-apollo/app-final/.gitignore
vendored
Normal file
46
community/learn/graphql-tutorials/tutorials/angular-apollo/app-final/.gitignore
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# compiled output
|
||||
/dist
|
||||
/tmp
|
||||
/out-tsc
|
||||
# Only exists if Bazel was run
|
||||
/bazel-out
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# profiling files
|
||||
chrome-profiler-events.json
|
||||
speed-measure-plugin.json
|
||||
|
||||
# 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
|
||||
.history/*
|
||||
|
||||
# misc
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
/coverage
|
||||
/libpeerconnection.log
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
testem.log
|
||||
/typings
|
||||
|
||||
# System Files
|
||||
.DS_Store
|
||||
Thumbs.db
|
@ -0,0 +1,120 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"app-boilerplate": {
|
||||
"projectType": "application",
|
||||
"schematics": {},
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"prefix": "app",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"outputPath": "dist/app-boilerplate",
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"aot": false,
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.css"
|
||||
],
|
||||
"scripts": []
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"extractCss": true,
|
||||
"namedChunks": false,
|
||||
"aot": true,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false,
|
||||
"buildOptimizer": true,
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "2mb",
|
||||
"maximumError": "5mb"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"options": {
|
||||
"browserTarget": "app-boilerplate:build"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "app-boilerplate:build:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "app-boilerplate:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"main": "src/test.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"karmaConfig": "karma.conf.js",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.css"
|
||||
],
|
||||
"scripts": []
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"options": {
|
||||
"tsConfig": [
|
||||
"tsconfig.app.json",
|
||||
"tsconfig.spec.json",
|
||||
"e2e/tsconfig.json"
|
||||
],
|
||||
"exclude": [
|
||||
"**/node_modules/**"
|
||||
]
|
||||
}
|
||||
},
|
||||
"e2e": {
|
||||
"builder": "@angular-devkit/build-angular:protractor",
|
||||
"options": {
|
||||
"protractorConfig": "e2e/protractor.conf.js",
|
||||
"devServerTarget": "app-boilerplate:serve"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"devServerTarget": "app-boilerplate:serve:production"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}},
|
||||
"defaultProject": "app-boilerplate"
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
|
||||
# For additional information regarding the format and rule options, please see:
|
||||
# https://github.com/browserslist/browserslist#queries
|
||||
|
||||
# You can see what browsers were selected by your queries by running:
|
||||
# npx browserslist
|
||||
|
||||
> 0.5%
|
||||
last 2 versions
|
||||
Firefox ESR
|
||||
not dead
|
||||
not IE 9-11 # For IE 9-11 support, remove 'not'.
|
@ -0,0 +1,32 @@
|
||||
// @ts-check
|
||||
// Protractor configuration file, see link for more information
|
||||
// https://github.com/angular/protractor/blob/master/lib/config.ts
|
||||
|
||||
const { SpecReporter } = require('jasmine-spec-reporter');
|
||||
|
||||
/**
|
||||
* @type { import("protractor").Config }
|
||||
*/
|
||||
exports.config = {
|
||||
allScriptsTimeout: 11000,
|
||||
specs: [
|
||||
'./src/**/*.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: require('path').join(__dirname, './tsconfig.json')
|
||||
});
|
||||
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
|
||||
}
|
||||
};
|
@ -0,0 +1,23 @@
|
||||
import { AppPage } from './app.po';
|
||||
import { browser, logging } from 'protractor';
|
||||
|
||||
describe('workspace-project App', () => {
|
||||
let page: AppPage;
|
||||
|
||||
beforeEach(() => {
|
||||
page = new AppPage();
|
||||
});
|
||||
|
||||
it('should display welcome message', () => {
|
||||
page.navigateTo();
|
||||
expect(page.getTitleText()).toEqual('Welcome to app-boilerplate!');
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
// Assert that there are no errors emitted from the browser
|
||||
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
|
||||
expect(logs).not.toContain(jasmine.objectContaining({
|
||||
level: logging.Level.SEVERE,
|
||||
} as logging.Entry));
|
||||
});
|
||||
});
|
@ -0,0 +1,11 @@
|
||||
import { browser, by, element } from 'protractor';
|
||||
|
||||
export class AppPage {
|
||||
navigateTo() {
|
||||
return browser.get(browser.baseUrl) as Promise<any>;
|
||||
}
|
||||
|
||||
getTitleText() {
|
||||
return element(by.css('app-root h1')).getText() as Promise<string>;
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../out-tsc/e2e",
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"types": [
|
||||
"jasmine",
|
||||
"jasminewd2",
|
||||
"node"
|
||||
]
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
// 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/app-boilerplate'),
|
||||
reports: ['html', 'lcovonly', 'text-summary'],
|
||||
fixWebpackSourcePaths: true
|
||||
},
|
||||
reporters: ['progress', 'kjhtml'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['Chrome'],
|
||||
singleRun: false,
|
||||
restartOnFileChange: true
|
||||
});
|
||||
};
|
12211
community/learn/graphql-tutorials/tutorials/angular-apollo/app-final/package-lock.json
generated
Normal file
12211
community/learn/graphql-tutorials/tutorials/angular-apollo/app-final/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,57 @@
|
||||
{
|
||||
"name": "app-boilerplate",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve --port 3000",
|
||||
"build": "ng build",
|
||||
"test": "ng test",
|
||||
"lint": "ng lint",
|
||||
"e2e": "ng e2e"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "~8.1.2",
|
||||
"@angular/common": "~8.1.2",
|
||||
"@angular/compiler": "~8.1.2",
|
||||
"@angular/core": "~8.1.2",
|
||||
"@angular/forms": "~8.1.2",
|
||||
"@angular/platform-browser": "~8.1.2",
|
||||
"@angular/platform-browser-dynamic": "~8.1.2",
|
||||
"@angular/router": "~8.1.2",
|
||||
"apollo-angular": "^1.6.0",
|
||||
"apollo-angular-link-http": "^1.8.0",
|
||||
"apollo-cache-inmemory": "^1.6.2",
|
||||
"apollo-client": "^2.6.3",
|
||||
"apollo-link": "^1.2.12",
|
||||
"apollo-link-ws": "^1.0.18",
|
||||
"auth0-js": "^9.10.0",
|
||||
"graphql": "^14.4.2",
|
||||
"graphql-tag": "^2.10.1",
|
||||
"rxjs": "~6.4.0",
|
||||
"subscriptions-transport-ws": "^0.9.16",
|
||||
"tslib": "^1.9.0",
|
||||
"zone.js": "~0.9.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "~0.801.2",
|
||||
"@angular/cli": "~8.1.2",
|
||||
"@angular/compiler-cli": "~8.1.2",
|
||||
"@angular/language-service": "~8.1.2",
|
||||
"@types/node": "~8.9.4",
|
||||
"@types/jasmine": "~3.3.8",
|
||||
"@types/jasminewd2": "~2.0.3",
|
||||
"codelyzer": "^5.0.0",
|
||||
"jasmine-core": "~3.4.0",
|
||||
"jasmine-spec-reporter": "~4.2.1",
|
||||
"karma": "~4.1.0",
|
||||
"karma-chrome-launcher": "~2.2.0",
|
||||
"karma-coverage-istanbul-reporter": "~2.0.1",
|
||||
"karma-jasmine": "~2.0.1",
|
||||
"karma-jasmine-html-reporter": "^1.4.0",
|
||||
"protractor": "~5.4.0",
|
||||
"ts-node": "~7.0.0",
|
||||
"tslint": "~5.15.0",
|
||||
"typescript": "~3.4.3"
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
import { Component, Output, EventEmitter } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'App',
|
||||
templateUrl: './app.component.html',
|
||||
})
|
||||
export class App {
|
||||
@Output('logout') logout: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
logoutHandler(item: any) {
|
||||
this.logout.emit(item);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
<Callback *ngIf="isAuthenticated === 'loading'"></Callback>
|
||||
<Login *ngIf="!isAuthenticated" (loginHandler)="login($event)"></Login>
|
||||
|
||||
<App *ngIf="isAuthenticated === true" (logout)="logout()"></App>
|
||||
|
||||
<router-outlet></router-outlet>
|
@ -0,0 +1,127 @@
|
||||
import { Component, OnInit, Input } from '@angular/core';
|
||||
import auth0 from 'auth0-js';
|
||||
|
||||
import {AUTH_CONFIG} from './auth0-variables';
|
||||
;
|
||||
@Component({
|
||||
selector: 'Auth0Wrapper',
|
||||
templateUrl: './Auth0Wrapper.template.html',
|
||||
})
|
||||
|
||||
export class Auth0Wrapper implements OnInit {
|
||||
auth0 = new auth0.WebAuth({
|
||||
domain: AUTH_CONFIG.domain,
|
||||
clientID: AUTH_CONFIG.clientId,
|
||||
redirectUri: AUTH_CONFIG.callbackUrl,
|
||||
audience: `https://${AUTH_CONFIG.domain}/userinfo`,
|
||||
responseType: 'token id_token',
|
||||
scope: 'openid profile'
|
||||
});
|
||||
isAuthenticated:any = false;// This can be true, false, 'loading'
|
||||
idToken = null;
|
||||
accessToken;
|
||||
expiresAt;
|
||||
@Input('location') location: any;
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
login() {
|
||||
this.auth0.authorize();
|
||||
}
|
||||
|
||||
handleAuthentication = () => {
|
||||
this.isAuthenticated= 'loading';
|
||||
|
||||
this.auth0.parseHash((err, authResult) => {
|
||||
if (authResult && authResult.accessToken && authResult.idToken) {
|
||||
this.setSession(authResult);
|
||||
} else if (err) {
|
||||
this.logout();
|
||||
console.error(err);
|
||||
alert(`Error: ${err.error} - ${err.errorDescription}`);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
getAccessToken() {
|
||||
return this.accessToken;
|
||||
}
|
||||
|
||||
getIdToken() {
|
||||
return this.idToken;
|
||||
}
|
||||
|
||||
setSession(authResult) {
|
||||
// Set isLoggedIn flag in localStorage
|
||||
localStorage.setItem('isLoggedIn', 'true');
|
||||
|
||||
// Set the time that the access token will expire at
|
||||
let expiresAt = (authResult.expiresIn * 1000) + new Date().getTime();
|
||||
this.accessToken = authResult.accessToken;
|
||||
this.idToken = authResult.idToken;
|
||||
this.expiresAt = expiresAt;
|
||||
localStorage.setItem('token', this.idToken);
|
||||
|
||||
// navigate to the home route
|
||||
this.isAuthenticated = true;
|
||||
this.idToken = authResult.idToken;
|
||||
}
|
||||
|
||||
renewSession() {
|
||||
this.isAuthenticated = 'loading';
|
||||
|
||||
this.auth0.checkSession({}, (err, authResult) => {
|
||||
if (authResult && authResult.accessToken && authResult.idToken) {
|
||||
this.setSession(authResult);
|
||||
} else if (err) {
|
||||
this.logout();
|
||||
console.log(err);
|
||||
alert(`Could not get a new token (${err.error}: ${err.error_description}).`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
logout() {
|
||||
// Remove tokens and expiry time
|
||||
this.accessToken = null;
|
||||
this.idToken = null;
|
||||
this.expiresAt = 0;
|
||||
|
||||
// Remove isLoggedIn flag from localStorage
|
||||
localStorage.removeItem('isLoggedIn');
|
||||
localStorage.removeItem('token');
|
||||
|
||||
this.auth0.logout({
|
||||
return_to: window.location.origin
|
||||
});
|
||||
|
||||
// navigate to the home route
|
||||
this.isAuthenticated = false;
|
||||
this.idToken = null;
|
||||
}
|
||||
|
||||
isExpired () {
|
||||
// Check whether the current time is past the
|
||||
// access token's expiry time
|
||||
let expiresAt = this.expiresAt;
|
||||
return new Date().getTime() > expiresAt;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
// If this is a callback URL then do the right things
|
||||
const location = window.location;
|
||||
if (location && location.pathname.startsWith('/callback') && /access_token|id_token|error/.test(location.hash)) {
|
||||
this.handleAuthentication();
|
||||
return;
|
||||
}
|
||||
|
||||
// On first load, check if we are already logged in and get the idTokens and things
|
||||
if(localStorage.getItem('isLoggedIn') === 'true') {
|
||||
this.renewSession();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
<div class="spinner">
|
||||
<img src="../../assets/loading.svg" alt="loading" />
|
||||
</div>
|
@ -0,0 +1,11 @@
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'Callback',
|
||||
templateUrl: './Callback.template.html'
|
||||
})
|
||||
|
||||
export class Callback {
|
||||
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
<div class="overlay">
|
||||
<div class="overlay-content">
|
||||
<div class="overlay-heading">
|
||||
Welcome to the GraphQL tutorial app
|
||||
</div>
|
||||
<div class="overlay-message">
|
||||
Please login to continue
|
||||
</div>
|
||||
<div class="overlay-action">
|
||||
<Button
|
||||
id="qsLoginBtn"
|
||||
type="button"
|
||||
class="btn-margin loginBtn btn btn-primary "
|
||||
(click)="loginHandlerWrapper()">
|
||||
Log In
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,15 @@
|
||||
import { Component, Output, EventEmitter } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'Login',
|
||||
templateUrl: './Login.template.html',
|
||||
})
|
||||
|
||||
export class Login {
|
||||
@Output('loginHandler') loginHandler: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
loginHandlerWrapper(item: any) {
|
||||
this.loginHandler.emit(item);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,8 @@
|
||||
<Button
|
||||
id="qsLogoutBtn"
|
||||
type="button"
|
||||
class="btn-margin logoutBtn btn btn-primary "
|
||||
(click)="logoutHandlerWrapper()"
|
||||
>
|
||||
Log Out
|
||||
</Button>
|
@ -0,0 +1,14 @@
|
||||
import { Component, EventEmitter, Output } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'LogoutBtn',
|
||||
templateUrl: './Logout.template.html',
|
||||
})
|
||||
|
||||
export class LogoutBtn {
|
||||
@Output('logoutHandler') logoutHandler: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
logoutHandlerWrapper(item: any) {
|
||||
this.logoutHandler.emit(item);
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
export const AUTH_CONFIG = {
|
||||
domain: 'graphql-tutorials.auth0.com',
|
||||
clientId: 'P38qnFo1lFAQJrzkun--wEzqljVNGcWW',
|
||||
callbackUrl: 'http://localhost:3000/callback'
|
||||
};
|
||||
|
@ -0,0 +1,14 @@
|
||||
<nav class="m-bottom-0 navbar navbar-default">
|
||||
<div class="container-fluid">
|
||||
<div class="navHeader navbar-header">
|
||||
<span class="navBrand navbar-brand">GraphQL Tutorial App</span>
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
<li role="presentation" class="">
|
||||
<a role="button" href="#">
|
||||
<LogoutBtn (logoutHandler)="logoutHandlerWrapper()"></LogoutBtn>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
@ -0,0 +1,14 @@
|
||||
import { Component, EventEmitter, Output } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'Header',
|
||||
templateUrl: './Header.template.html',
|
||||
})
|
||||
|
||||
export class Header {
|
||||
@Output('logoutHandler') logoutHandler: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
logoutHandlerWrapper(item: any) {
|
||||
this.logoutHandler.emit(item);
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
<div class="userInfo">
|
||||
<div class="userImg">
|
||||
<i class="far fa-user" ></i>
|
||||
</div>
|
||||
<div class="userName">
|
||||
{{user.name}}
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,10 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'OnlineUser',
|
||||
templateUrl: './OnlineUser.template.html',
|
||||
})
|
||||
|
||||
export class OnlineUser {
|
||||
@Input('user') user: any;
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
<div class="onlineUsersWrapper" *ngIf="!loading">
|
||||
<div class="sliderHeader">
|
||||
Online users - {{onlineUsers.length}}
|
||||
</div>
|
||||
<ng-container *ngFor="let user of onlineUsers; let i = index" [attr.data-index]="i">
|
||||
<OnlineUser [user]="user"></OnlineUser>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div *ngIf="loading">Loading...</div>
|
||||
|
@ -0,0 +1,71 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { Apollo } from 'apollo-angular';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
const SUBSCRIBE_TO_ONLINE_USERS = gql`
|
||||
subscription getOnlineUsers {
|
||||
online_users(order_by: {user: {name: asc }}) {
|
||||
id
|
||||
user {
|
||||
name
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
@Component({
|
||||
selector: 'OnlineUsersWrapper',
|
||||
templateUrl: './OnlineUsersWrapper.template.html',
|
||||
})
|
||||
|
||||
export class OnlineUsersWrapper implements OnInit, OnDestroy {
|
||||
onlineUsers = []
|
||||
onlineIndicator: any;
|
||||
loading: boolean = true;
|
||||
|
||||
private querySubscription: Subscription;
|
||||
|
||||
constructor(private apollo: Apollo) {}
|
||||
|
||||
ngOnInit(){
|
||||
this.onlineIndicator = setInterval(() => this.updateLastSeen(), 30000);
|
||||
this.querySubscription = this.apollo.subscribe({
|
||||
query: SUBSCRIBE_TO_ONLINE_USERS,
|
||||
}).subscribe(({ data, loading }) => {
|
||||
if(data) {
|
||||
const users = data.online_users;
|
||||
this.loading = loading;
|
||||
this.onlineUsers = [];
|
||||
users.forEach((u, index) => {
|
||||
this.onlineUsers.push(u.user)
|
||||
})
|
||||
}
|
||||
console.log('got data ', data);
|
||||
},(error) => {
|
||||
console.log('there was an error sending the query', error);
|
||||
});
|
||||
}
|
||||
|
||||
updateLastSeen() {
|
||||
// Use the apollo client to run a mutation to update the last_seen value
|
||||
const UPDATE_LASTSEEN_MUTATION=gql`
|
||||
mutation updateLastSeen ($now: timestamptz!) {
|
||||
update_users(where: {}, _set: {last_seen: $now}) {
|
||||
affected_rows
|
||||
}
|
||||
}`;
|
||||
this.apollo.mutate({
|
||||
mutation: UPDATE_LASTSEEN_MUTATION,
|
||||
variables: {now: (new Date()).toISOString()}
|
||||
}).subscribe(({ data, loading }) => {
|
||||
console.log('got data ', data);
|
||||
},(error) => {
|
||||
console.log('there was an error sending the query', error);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.querySubscription.unsubscribe();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
<li>
|
||||
<div class="userInfoPublic" title={todo.user.name}>
|
||||
@{{todo.user.name}}
|
||||
</div>
|
||||
|
||||
<div [ngClass]="todo.is_completed ? 'labelContent completed' : 'labelContent'">
|
||||
<div>
|
||||
{{todo.title}}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
@ -0,0 +1,10 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'TaskItem',
|
||||
templateUrl: './TaskItem.template.html',
|
||||
})
|
||||
|
||||
export class TaskItem {
|
||||
@Input('todo') todo: any;
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
<div class="footerList">
|
||||
<span> {{itemCount}} item{{itemCount !== 1 ? "s" : ""}}</span>
|
||||
|
||||
<ul>
|
||||
<li (click)="filterResultsHandler('all')">
|
||||
<a [ngClass]="currentFilter === 'all' ? 'selected' : ''">
|
||||
All
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li (click)="filterResultsHandler('active')">
|
||||
<a [ngClass]= "currentFilter === 'active' ? 'selected' : ''">
|
||||
Active
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li (click)="filterResultsHandler('completed')">
|
||||
<a [ngClass]="currentFilter === 'completed' ? 'selected' : ''">
|
||||
Completed
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<button (click)="clearCompletedFnWrapper()" class="clearComp">
|
||||
Clear completed
|
||||
</button>
|
||||
</div>
|
@ -0,0 +1,41 @@
|
||||
import { Component, Input, Output, EventEmitter, OnChanges } from '@angular/core';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'TodoFilters',
|
||||
templateUrl: './TodoFilters.template.html',
|
||||
})
|
||||
|
||||
export class TodoFilters implements OnChanges{
|
||||
@Input('todos') todos: any;
|
||||
@Input('currentFilter') currentFilter: any;
|
||||
@Output('filterResultsFn') filterResultsFn: EventEmitter<any> = new EventEmitter();;
|
||||
@Output('clearCompletedFn') clearCompletedFn: EventEmitter<any> = new EventEmitter();;
|
||||
|
||||
itemCount: any;
|
||||
|
||||
ngOnChanges(){
|
||||
this.updateItemCount();
|
||||
}
|
||||
|
||||
updateItemCount() {
|
||||
this.itemCount = this.todos.length;
|
||||
const activeTodos = this.todos.filter(todo => todo.is_completed !== true);
|
||||
|
||||
if (this.currentFilter === 'active') {
|
||||
this.itemCount = activeTodos.length;
|
||||
} else if (this.currentFilter === 'completed') {
|
||||
this.itemCount = this.todos.length - activeTodos.length;
|
||||
}
|
||||
}
|
||||
|
||||
clearCompletedFnWrapper(item: any) {
|
||||
this.clearCompletedFn.emit(item);
|
||||
|
||||
}
|
||||
|
||||
filterResultsHandler(filter) {
|
||||
this.filterResultsFn.emit({ event:event, filter: filter });
|
||||
;
|
||||
};
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
<form class="formInput" (submit)="addTodo($event)">
|
||||
<input
|
||||
class="input"
|
||||
placeholder="What needs to be done?"
|
||||
(disabled)= "loading"
|
||||
[(ngModel)]="todoInput"
|
||||
[ngModelOptions]="{standalone: true}"
|
||||
/>
|
||||
<i class="inputMarker fa fa-angle-right"></i>
|
||||
</form>
|
@ -0,0 +1,64 @@
|
||||
import { Component, OnInit, Input } from '@angular/core';
|
||||
import { Apollo } from 'apollo-angular';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import {GET_MY_TODOS} from './TodoPrivateList';
|
||||
|
||||
const ADD_TODO = gql `
|
||||
mutation ($todo: String!, $isPublic: Boolean!) {
|
||||
insert_todos(objects: {title: $todo, is_public: $isPublic}) {
|
||||
affected_rows
|
||||
returning {
|
||||
id
|
||||
title
|
||||
created_at
|
||||
is_completed
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@Component({
|
||||
selector: 'TodoInput',
|
||||
templateUrl: './TodoInput.template.html',
|
||||
})
|
||||
|
||||
export class TodoInput {
|
||||
@Input('isPublic') isPublic: any = false;
|
||||
todoInput: any= '';
|
||||
loading: boolean = true;
|
||||
|
||||
constructor(private apollo: Apollo) {}
|
||||
|
||||
addTodo(e) {
|
||||
e.preventDefault();
|
||||
this.apollo.mutate({
|
||||
mutation: ADD_TODO,
|
||||
variables: {
|
||||
todo: this.todoInput,
|
||||
isPublic: this.isPublic
|
||||
},
|
||||
update: (cache, {data}) => {
|
||||
|
||||
if(this.isPublic) return null;
|
||||
|
||||
const existingTodos : any = cache.readQuery({
|
||||
query: GET_MY_TODOS
|
||||
});
|
||||
|
||||
const newTodo = data.insert_todos.returning[0];
|
||||
cache.writeQuery({
|
||||
query: GET_MY_TODOS,
|
||||
data: {todos: [newTodo, ...existingTodos.todos]}
|
||||
});
|
||||
},
|
||||
|
||||
}).subscribe(({ data, loading }) => {
|
||||
this.loading = loading;
|
||||
this.todoInput = '';
|
||||
console.log('got data ', data);
|
||||
},(error) => {
|
||||
console.log('there was an error sending the query', error);
|
||||
});
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user