├── src ├── assets │ └── .gitkeep ├── app │ ├── app.component.css │ ├── app.component.html │ ├── app.module.ts │ ├── app.component.spec.ts │ └── app.component.ts ├── favicon.ico ├── typings.d.ts ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── styles.css ├── tsconfig.app.json ├── components │ ├── picture │ │ ├── picture.ts │ │ └── picture.html │ └── picture_upload │ │ ├── picture_upload.html │ │ └── picture_upload.ts ├── main.ts ├── tsconfig.spec.json ├── index.html ├── test.ts ├── services │ └── cosmic_config.ts └── polyfills.ts ├── img2.png ├── img-deploy.png ├── app.json ├── app.js ├── e2e ├── tsconfig.e2e.json ├── app.po.ts └── app.e2e-spec.ts ├── .editorconfig ├── prepare.js ├── tsconfig.json ├── .gitignore ├── README.md ├── protractor.conf.js ├── karma.conf.js ├── .angular-cli.json ├── package.json ├── tslint.json └── post.md /src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/app.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmicjs/angular-image-feed/master/img2.png -------------------------------------------------------------------------------- /img-deploy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmicjs/angular-image-feed/master/img-deploy.png -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmicjs/angular-image-feed/master/src/favicon.ico -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /* SystemJS module definition */ 2 | declare var module: NodeModule; 3 | interface NodeModule { 4 | id: string; 5 | } 6 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "dokku": { 4 | "predeploy": "node prepare.js && ng build --aot --prod" 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const app = express() 3 | 4 | app.use(express.static('./dist')); 5 | 6 | app.listen(process.env.PORT, function () { 7 | }); -------------------------------------------------------------------------------- /e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "node" 10 | ] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | 2 | export const environment = { 3 | production: true, 4 | write_key: 'jaKaNJi7LXhALkjJlZa2JvuSPwdkHQbhwzbMcDzB6LHY6uZlsj', 5 | bucket_name: 'angular-gallery', 6 | photos_type: 'photos' 7 | }; 8 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
5 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | .picture-item { 3 | width: 100% !important; 4 | margin: 20px 0 !important; 5 | } 6 | 7 | .picture-upload { 8 | position: relative; 9 | min-height: 68px; 10 | } -------------------------------------------------------------------------------- /e2e/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class CosmicAngularPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "es2015", 6 | "baseUrl": "", 7 | "types": [] 8 | }, 9 | "exclude": [ 10 | "test.ts", 11 | "**/*.spec.ts" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /src/components/picture/picture.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'picture', 5 | templateUrl: './picture.html' 6 | }) 7 | export class Picture { 8 | @Input() picture: any; 9 | 10 | constructor() { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/components/picture/picture.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{ picture.title }} 4 |
5 |
6 |
{{ picture.title }}
7 |
8 |
-------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule); 12 | -------------------------------------------------------------------------------- /e2e/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { CosmicAngularPage } from './app.po'; 2 | 3 | describe('cosmic-angular App', () => { 4 | let page: CosmicAngularPage; 5 | 6 | beforeEach(() => { 7 | page = new CosmicAngularPage(); 8 | }); 9 | 10 | it('should display welcome message', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toEqual('Welcome to app!!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "baseUrl": "", 8 | "types": [ 9 | "jasmine", 10 | "node" 11 | ] 12 | }, 13 | "files": [ 14 | "test.ts" 15 | ], 16 | "include": [ 17 | "**/*.spec.ts", 18 | "**/*.d.ts" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /prepare.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | 3 | var str = ` 4 | export const environment = { 5 | production: true, 6 | write_key: '${process.env.COSMIC_WRITE_KEY}', 7 | bucket_name: '${process.env.COSMIC_BUCKET}', 8 | photos_type: 'photos' 9 | }; 10 | `; 11 | fs.writeFile("./src/environments/environment.prod.ts", str, function(err) { 12 | if(err) { 13 | return console.log(err); 14 | } 15 | console.log("The file was saved!"); 16 | }); -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "outDir": "./dist/out-tsc", 5 | "baseUrl": "src", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "moduleResolution": "node", 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "target": "es5", 12 | "typeRoots": [ 13 | "node_modules/@types" 14 | ], 15 | "lib": [ 16 | "es2016", 17 | "dom" 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build. 2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `.angular-cli.json`. 5 | 6 | export const environment = { 7 | production: false, 8 | write_key: 'jaKaNJi7LXhALkjJlZa2JvuSPwdkHQbhwzbMcDzB6LHY6uZlsj', 9 | bucket_name: 'angular-gallery', 10 | photos_type: 'photos' 11 | }; 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | testem.log 34 | /typings 35 | 36 | # e2e 37 | /e2e/*.js 38 | /e2e/*.map 39 | 40 | # System Files 41 | .DS_Store 42 | Thumbs.db 43 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | import { InfiniteScrollModule } from 'ngx-infinite-scroll'; 4 | 5 | import { Picture } from '../components/picture/picture'; 6 | import { PictureUpload } from '../components/picture_upload/picture_upload'; 7 | import { AppComponent } from './app.component'; 8 | 9 | import { CosmicConfigService } from '../services/cosmic_config'; 10 | 11 | @NgModule({ 12 | declarations: [ 13 | AppComponent, 14 | Picture, 15 | PictureUpload 16 | ], 17 | imports: [ 18 | BrowserModule, 19 | InfiniteScrollModule 20 | ], 21 | providers: [CosmicConfigService], 22 | bootstrap: [AppComponent] 23 | }) 24 | export class AppModule { } 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular Image Feed 2 | ![Angular Image Feed](https://cosmicjs.com/uploads/a41f75b0-61cb-11e7-82bf-6b4c51607410-angular-image-feed.jpg) 3 | An Angular JS image feed that includes upload ability, caption creation and infinite scroll. Powered by the [Cosmic JS API](https://cosmicjs.com). 4 | ### [View Demo](https://cosmicjs.com/apps/angular-js-image-feed) 5 | 6 | # Running locally 7 | 8 | First of all, you have to be sure you have node >= 6.x installed and npm >= 4.0, than run the following commands: 9 | 10 | ```bash 11 | npm install -g @angular/cli 12 | git clone https://github.com/cosmicjs/angular-image-feed 13 | cd angular-image-feed 14 | npm install 15 | ng serve --open 16 | ``` 17 | The most recent ng cli version at the article creation moment was 1.1.3. 18 | Browser window will open automatically once you'll run the last command. 19 | -------------------------------------------------------------------------------- /protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './e2e/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: 'e2e/tsconfig.e2e.json' 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Angular Image Feed 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 | 16 |
17 | 18 |    Proudly powered by Cosmic JS 19 | 20 |
21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /src/components/picture_upload/picture_upload.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 | 7 |
8 |
9 |
10 |
11 | 12 |
13 |
14 |
15 | 16 |
17 |
18 |
Upload is in progress
19 |
20 |
-------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/0.13/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular/cli'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular/cli/plugins/karma') 14 | ], 15 | client:{ 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | reports: [ 'html', 'lcovonly' ], 20 | fixWebpackSourcePaths: true 21 | }, 22 | angularCli: { 23 | environment: 'dev' 24 | }, 25 | reporters: ['progress', 'kjhtml'], 26 | port: 9876, 27 | colors: true, 28 | logLevel: config.LOG_INFO, 29 | autoWatch: true, 30 | browsers: ['Chrome'], 31 | singleRun: false 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } from '@angular/core/testing'; 2 | 3 | import { AppComponent } from './app.component'; 4 | 5 | describe('AppComponent', () => { 6 | beforeEach(async(() => { 7 | TestBed.configureTestingModule({ 8 | declarations: [ 9 | AppComponent 10 | ], 11 | }).compileComponents(); 12 | })); 13 | 14 | it('should create the app', async(() => { 15 | const fixture = TestBed.createComponent(AppComponent); 16 | const app = fixture.debugElement.componentInstance; 17 | expect(app).toBeTruthy(); 18 | })); 19 | 20 | it(`should have as title 'app'`, async(() => { 21 | const fixture = TestBed.createComponent(AppComponent); 22 | const app = fixture.debugElement.componentInstance; 23 | expect(app.title).toEqual('app'); 24 | })); 25 | 26 | it('should render title in a h1 tag', async(() => { 27 | const fixture = TestBed.createComponent(AppComponent); 28 | fixture.detectChanges(); 29 | const compiled = fixture.debugElement.nativeElement; 30 | expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!!'); 31 | })); 32 | }); 33 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/long-stack-trace-zone'; 4 | import 'zone.js/dist/proxy.js'; 5 | import 'zone.js/dist/sync-test'; 6 | import 'zone.js/dist/jasmine-patch'; 7 | import 'zone.js/dist/async-test'; 8 | import 'zone.js/dist/fake-async-test'; 9 | import { getTestBed } from '@angular/core/testing'; 10 | import { 11 | BrowserDynamicTestingModule, 12 | platformBrowserDynamicTesting 13 | } from '@angular/platform-browser-dynamic/testing'; 14 | 15 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. 16 | declare const __karma__: any; 17 | declare const require: any; 18 | 19 | // Prevent Karma from running prematurely. 20 | __karma__.loaded = function () {}; 21 | 22 | // First, initialize the Angular testing environment. 23 | getTestBed().initTestEnvironment( 24 | BrowserDynamicTestingModule, 25 | platformBrowserDynamicTesting() 26 | ); 27 | // Then we find all the tests. 28 | const context = require.context('./', true, /\.spec\.ts$/); 29 | // And load the modules. 30 | context.keys().map(context); 31 | // Finally, start Karma to run the tests. 32 | __karma__.start(); 33 | -------------------------------------------------------------------------------- /.angular-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "project": { 3 | "name": "cosmic-angular" 4 | }, 5 | "apps": [ 6 | { 7 | "root": "src", 8 | "outDir": "dist", 9 | "assets": [ 10 | "assets", 11 | "favicon.ico" 12 | ], 13 | "index": "index.html", 14 | "main": "main.ts", 15 | "polyfills": "polyfills.ts", 16 | "test": "test.ts", 17 | "tsconfig": "tsconfig.app.json", 18 | "testTsconfig": "tsconfig.spec.json", 19 | "prefix": "app", 20 | "styles": [ 21 | "styles.css" 22 | ], 23 | "scripts": [], 24 | "environmentSource": "environments/environment.ts", 25 | "environments": { 26 | "dev": "environments/environment.ts", 27 | "prod": "environments/environment.prod.ts" 28 | } 29 | } 30 | ], 31 | "e2e": { 32 | "protractor": { 33 | "config": "./protractor.conf.js" 34 | } 35 | }, 36 | "lint": [ 37 | { 38 | "project": "src/tsconfig.app.json" 39 | }, 40 | { 41 | "project": "src/tsconfig.spec.json" 42 | }, 43 | { 44 | "project": "e2e/tsconfig.e2e.json" 45 | } 46 | ], 47 | "test": { 48 | "karma": { 49 | "config": "./karma.conf.js" 50 | } 51 | }, 52 | "defaults": { 53 | "styleExt": "css", 54 | "component": {} 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/services/cosmic_config.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {environment} from '../environments/environment'; 3 | 4 | @Injectable() 5 | export class CosmicConfigService { 6 | private write_key; 7 | private bucket_name; 8 | private photos_type; 9 | 10 | constructor() { 11 | this.photos_type = environment.photos_type; 12 | this.write_key = environment.write_key; 13 | this.bucket_name = environment.bucket_name; 14 | } 15 | 16 | public getReadCfg(): any { 17 | return { 18 | bucket: { 19 | slug: this.bucket_name 20 | } 21 | }; 22 | } 23 | 24 | public getWriteCfg(): any { 25 | return { 26 | bucket: { 27 | slug: this.bucket_name, 28 | write_key: this.write_key 29 | } 30 | }; 31 | } 32 | 33 | public buildPhotoUploadObj(title, file): any { 34 | return { 35 | write_key: this.write_key, 36 | type_slug: this.photos_type, 37 | title: title, 38 | metafields: [{ 39 | key: 'picture', 40 | type: 'file', 41 | value: file 42 | }] 43 | }; 44 | } 45 | 46 | getPhotoSlug() { 47 | return this.photos_type; 48 | } 49 | } -------------------------------------------------------------------------------- /src/components/picture_upload/picture_upload.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Output, EventEmitter } from '@angular/core'; 2 | import Cosmic from 'cosmicjs'; 3 | import { CosmicConfigService } from '../../services/cosmic_config'; 4 | 5 | @Component({ 6 | selector: 'picture-upload', 7 | templateUrl: './picture_upload.html' 8 | }) 9 | export class PictureUpload { 10 | private fl; 11 | private title; 12 | public uploading; 13 | @Output() onUpload = new EventEmitter(); 14 | 15 | constructor( 16 | private cosmicConfig: CosmicConfigService 17 | ) { 18 | this.uploading = false; 19 | this.fl = null; 20 | this.title = ""; 21 | } 22 | 23 | onFileChange(ev) { 24 | if (ev.target.files && ev.target.files.length) { 25 | this.fl = ev.target.files[0]; 26 | } 27 | } 28 | 29 | upload() { 30 | this.uploading = true; 31 | Cosmic.addMedia(this.cosmicConfig.getWriteCfg(), { 32 | media: this.fl, 33 | folder: this.fl.name 34 | }, (error, response) => { 35 | Cosmic.addObject(this.cosmicConfig.getWriteCfg(), 36 | this.cosmicConfig.buildPhotoUploadObj(this.title, response.body.media.name), 37 | (error, response) => { 38 | this.title = ''; 39 | this.fl = null; 40 | this.uploading = false; 41 | this.onUpload.emit({}); 42 | }); 43 | }); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import Cosmic from 'cosmicjs'; 3 | import {CosmicConfigService} from '../services/cosmic_config'; 4 | 5 | @Component({ 6 | selector: 'app-root', 7 | templateUrl: './app.component.html', 8 | styleUrls: ['./app.component.css'] 9 | }) 10 | export class AppComponent { 11 | public items = []; 12 | page = 0; 13 | page_size = 2; 14 | scrollEnabled = true; 15 | 16 | constructor( 17 | public cosmicCfg: CosmicConfigService 18 | ) { 19 | this.reload(); 20 | } 21 | 22 | reload() { 23 | this.items = []; 24 | this.page = 0; 25 | this.scrollEnabled = true; 26 | let params = { 27 | type_slug: this.cosmicCfg.getPhotoSlug(), 28 | limit: this.page_size, 29 | skip: 0 30 | }; 31 | Cosmic.getObjectType(this.cosmicCfg.getReadCfg(), params, (err, res) => { 32 | this.items = res.objects.all; 33 | }); 34 | } 35 | 36 | onUpload() { 37 | this.reload(); 38 | } 39 | 40 | onScroll() { 41 | if (!this.scrollEnabled) { 42 | return; 43 | } 44 | this.page++; 45 | let params = { 46 | type_slug: this.cosmicCfg.getPhotoSlug(), 47 | limit: this.page_size, 48 | skip: this.page * this.page_size 49 | }; 50 | Cosmic.getObjectType(this.cosmicCfg.getReadCfg(), params, (err, res) => { 51 | if (res.objects && res.objects.all) { 52 | res.objects.all.forEach((itm) => { 53 | this.items.push(itm); 54 | }); 55 | } 56 | else { 57 | this.scrollEnabled = false; 58 | } 59 | }); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cosmic-angular", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "ng": "ng", 7 | "build": "ng build", 8 | "test": "ng test", 9 | "lint": "ng lint", 10 | "e2e": "ng e2e", 11 | "start": "node app.js" 12 | }, 13 | "engines": { 14 | "node": "6.9.4", 15 | "npm": "4.2.x" 16 | }, 17 | "private": true, 18 | "dependencies": { 19 | "@angular/animations": "^4.0.0", 20 | "@angular/cli": "^1.1.3", 21 | "@angular/compiler-cli": "^4.0.0", 22 | "@angular/common": "^4.0.0", 23 | "@angular/compiler": "^4.0.0", 24 | "@angular/core": "^4.0.0", 25 | "@angular/forms": "^4.0.0", 26 | "@angular/http": "^4.0.0", 27 | "@angular/platform-browser": "^4.0.0", 28 | "@angular/platform-browser-dynamic": "^4.0.0", 29 | "@angular/router": "^4.0.0", 30 | "core-js": "^2.4.1", 31 | "cosmicjs": "^2.39.91", 32 | "ngx-infinite-scroll": "^0.5.1", 33 | "rxjs": "^5.1.0", 34 | "zone.js": "^0.8.4" 35 | }, 36 | "devDependencies": { 37 | "@angular/cli": "1.1.3", 38 | "@angular/language-service": "^4.0.0", 39 | "@types/jasmine": "2.5.45", 40 | "@types/node": "~6.0.60", 41 | "codelyzer": "~3.0.1", 42 | "jasmine-core": "~2.6.2", 43 | "jasmine-spec-reporter": "~4.1.0", 44 | "karma": "~1.7.0", 45 | "karma-chrome-launcher": "~2.1.1", 46 | "karma-cli": "~1.0.1", 47 | "karma-coverage-istanbul-reporter": "^1.2.1", 48 | "karma-jasmine": "~1.1.0", 49 | "karma-jasmine-html-reporter": "^0.2.2", 50 | "protractor": "~5.1.2", 51 | "ts-node": "~3.0.4", 52 | "tslint": "~5.3.2", 53 | "typescript": "~2.3.3" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/ 22 | // import 'core-js/es6/symbol'; 23 | // import 'core-js/es6/object'; 24 | // import 'core-js/es6/function'; 25 | // import 'core-js/es6/parse-int'; 26 | // import 'core-js/es6/parse-float'; 27 | // import 'core-js/es6/number'; 28 | // import 'core-js/es6/math'; 29 | // import 'core-js/es6/string'; 30 | // import 'core-js/es6/date'; 31 | // import 'core-js/es6/array'; 32 | // import 'core-js/es6/regexp'; 33 | // import 'core-js/es6/map'; 34 | // import 'core-js/es6/weak-map'; 35 | // import 'core-js/es6/set'; 36 | 37 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 38 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 39 | 40 | /** IE10 and IE11 requires the following to support `@angular/animation`. */ 41 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 42 | 43 | 44 | /** Evergreen browsers require these. **/ 45 | import 'core-js/es6/reflect'; 46 | import 'core-js/es7/reflect'; 47 | 48 | 49 | /** ALL Firefox browsers require the following to support `@angular/animation`. **/ 50 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 51 | 52 | 53 | 54 | /*************************************************************************************************** 55 | * Zone JS is required by Angular itself. 56 | */ 57 | import 'zone.js/dist/zone'; // Included with Angular CLI. 58 | 59 | 60 | 61 | /*************************************************************************************************** 62 | * APPLICATION IMPORTS 63 | */ 64 | 65 | /** 66 | * Date, currency, decimal and percent pipes. 67 | * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 68 | */ 69 | // import 'intl'; // Run `npm install --save intl`. 70 | /** 71 | * Need to import at least one locale-data with intl. 72 | */ 73 | // import 'intl/locale-data/jsonp/en'; 74 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "arrow-return-shorthand": true, 7 | "callable-types": true, 8 | "class-name": true, 9 | "comment-format": [ 10 | true, 11 | "check-space" 12 | ], 13 | "curly": true, 14 | "eofline": true, 15 | "forin": true, 16 | "import-blacklist": [ 17 | true, 18 | "rxjs" 19 | ], 20 | "import-spacing": true, 21 | "indent": [ 22 | true, 23 | "spaces" 24 | ], 25 | "interface-over-type-literal": true, 26 | "label-position": true, 27 | "max-line-length": [ 28 | true, 29 | 140 30 | ], 31 | "member-access": false, 32 | "member-ordering": [ 33 | true, 34 | "static-before-instance", 35 | "variables-before-functions" 36 | ], 37 | "no-arg": true, 38 | "no-bitwise": true, 39 | "no-console": [ 40 | true, 41 | "debug", 42 | "info", 43 | "time", 44 | "timeEnd", 45 | "trace" 46 | ], 47 | "no-construct": true, 48 | "no-debugger": true, 49 | "no-duplicate-super": true, 50 | "no-empty": false, 51 | "no-empty-interface": true, 52 | "no-eval": true, 53 | "no-inferrable-types": [ 54 | true, 55 | "ignore-params" 56 | ], 57 | "no-misused-new": true, 58 | "no-non-null-assertion": true, 59 | "no-shadowed-variable": true, 60 | "no-string-literal": false, 61 | "no-string-throw": true, 62 | "no-switch-case-fall-through": true, 63 | "no-trailing-whitespace": true, 64 | "no-unnecessary-initializer": true, 65 | "no-unused-expression": true, 66 | "no-use-before-declare": true, 67 | "no-var-keyword": true, 68 | "object-literal-sort-keys": false, 69 | "one-line": [ 70 | true, 71 | "check-open-brace", 72 | "check-catch", 73 | "check-else", 74 | "check-whitespace" 75 | ], 76 | "prefer-const": true, 77 | "quotemark": [ 78 | true, 79 | "single" 80 | ], 81 | "radix": true, 82 | "semicolon": [ 83 | "always" 84 | ], 85 | "triple-equals": [ 86 | true, 87 | "allow-null-check" 88 | ], 89 | "typedef-whitespace": [ 90 | true, 91 | { 92 | "call-signature": "nospace", 93 | "index-signature": "nospace", 94 | "parameter": "nospace", 95 | "property-declaration": "nospace", 96 | "variable-declaration": "nospace" 97 | } 98 | ], 99 | "typeof-compare": true, 100 | "unified-signatures": true, 101 | "variable-name": false, 102 | "whitespace": [ 103 | true, 104 | "check-branch", 105 | "check-decl", 106 | "check-operator", 107 | "check-separator", 108 | "check-type" 109 | ], 110 | "directive-selector": [ 111 | true, 112 | "attribute", 113 | "app", 114 | "camelCase" 115 | ], 116 | "component-selector": [ 117 | true, 118 | "element", 119 | "app", 120 | "kebab-case" 121 | ], 122 | "use-input-property-decorator": true, 123 | "use-output-property-decorator": true, 124 | "use-host-property-decorator": true, 125 | "no-input-rename": true, 126 | "no-output-rename": true, 127 | "use-life-cycle-interface": true, 128 | "use-pipe-transform-interface": true, 129 | "component-class-suffix": true, 130 | "directive-class-suffix": true, 131 | "no-access-missing-member": true, 132 | "templates-use-public": true, 133 | "invoke-injectable": true 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /post.md: -------------------------------------------------------------------------------- 1 | In this tutorial I’m going to show you users-driven photo gallery, which will be hosted on CosmicJS App Server. 2 | 3 | # Prerequisites 4 | 5 | You’ll need the node JS, npm and Angular cli pre-installed. Make sure you already have them before start. Please refer to [Angular docs](https://angular.io/guide/quickstart) how to do this. 6 | 7 | # Getting Started 8 | 9 | First of all we’ll need to create the Angular project. We’ll use ng cli to do it. So once you’ll have all prerequisites installed, you’ll need to setup the new Angular project: 10 | 11 | ```bash 12 | ng new cosmic-angular 13 | ``` 14 | 15 | After you’ll setup this project you’ll be able to run 16 | 17 | ```bash 18 | cd cosmic-angular 19 | ng serve --open 20 | ``` 21 | 22 | And play with your app in browser 23 | 24 | # Doing everything using the existing git repo 25 | 26 | First of all, you have to be sure you have node > 6.x installed, than run the following commands: 27 | 28 | ```bash 29 | npm install -g @angular/cli 30 | git clone https://github.com/cosmicjs/angular-image-feed 31 | cd cosmic-angular 32 | npm install 33 | ng serve --open 34 | ``` 35 | The most recent ng cli version at the article creation moment was 1.1.3. 36 | Browser window will open automatically once you'll run the last command 37 | 38 | # Setting up Cosmic JS library 39 | 40 | First of all, install Cosmic JS Angular/JavaScript library 41 | 42 | ```bash 43 | npm install cosmicjs --save 44 | ``` 45 | 46 | Now you should be able to import Cosmic object and perform Cosmic JS API calls like following: 47 | 48 | ```typescript 49 | import Cosmic from 'cosmicjs'; 50 | const bucket = { slug: 'your-bucket-slug' }; 51 | 52 | Cosmic.getObjects({ bucket }, (err, res) => { 53 | console.log(res.objects); 54 | }); 55 | ``` 56 | 57 | # Setting up things with Cosmic JS 58 | 59 | Create the bucket and remeber the bucket name (`‘cosmic-angular’` in our case): 60 | 61 | Than create a new object type named Photo and please remember the object type slug (photos’). 62 | 63 | We also need a way to store the picture itself. Please enter the “Metafields Template” tab and add “Image/File” type metafield with key `'photo'`. This metafield will store the image. We don’t need anything more, so just set the name and save object type. 64 | After save you’ll be redirected to ‘New Photo’ page. Create some photos using this page and save them - we'll use them as test data. 65 | 66 | You'll also need to create the bucket write key. It's necessary to allow users upload pictures and create photo objects. Open Settings page and click 'Generate new key' on API Write Access Key than copy generated key and save the changes. 67 | 68 | ![](img2.png) 69 | 70 | # Angular environments 71 | 72 | Edit the `src/environments/environment.ts` to match the following: 73 | ```typescript 74 | export const environment = { 75 | production: false, 76 | write_key: 'YOURWRITEKEY', 77 | bucket_name: 'YOURBUCKETNAME', 78 | photos_type: 'photos' 79 | }; 80 | 81 | ``` 82 | 83 | # Configuration service for Angular 84 | 85 | We're planning to use CosmicJS objects in more than one Angular component. In such case makes sense to create a dedicated configuration service and store all Cosmic JS related things such as bucket name, write key, etc in a single place. Let's create `src/services/cosmic_config.ts` with the following contents: 86 | 87 | ```typescript 88 | import {Injectable} from '@angular/core'; 89 | 90 | @Injectable() 91 | export class CosmicConfigService { 92 | private write_key; 93 | private bucket_name; 94 | private photos_type; 95 | 96 | constructor() { 97 | this.photos_type = environment.photos_type; 98 | this.write_key = environment.write_key; 99 | this.bucket_name = environment.bucket_name 100 | } 101 | 102 | public getReadCfg(): any { 103 | return { 104 | bucket: { 105 | slug: this.bucket_name 106 | } 107 | }; 108 | } 109 | 110 | public getWriteCfg(): any { 111 | return { 112 | bucket: { 113 | slug: this.bucket_name, 114 | write_key: this.write_key 115 | } 116 | }; 117 | } 118 | 119 | public buildPhotoUploadObj(title, file): any { 120 | return { 121 | write_key: this.write_key, 122 | type_slug: this.photos_type, 123 | title: title, 124 | metafields: [{ 125 | key: 'picture', 126 | type: 'file', 127 | value: file 128 | }] 129 | }; 130 | } 131 | 132 | getPhotoSlug() { 133 | return this.photos_type; 134 | } 135 | } 136 | ``` 137 | 138 | This service has a few methods: 139 | 140 | * `getReadCfg` - returns config object for reading data 141 | * `getWriteCfg` - return config object for writing data (with write_key specified) 142 | * `buildPhotoUploadObj` - builds object to create photo using file name and title 143 | * `getPhotoSlug` - returns object type slug for photos 144 | 145 | We'll call these methods from our Angular components. 146 | 147 | # View the gallery - Angular part 148 | 149 | Create `src/components/picture/picture.ts` file with the following content: 150 | 151 | ```typescript 152 | import { Component, Input } from '@angular/core'; 153 | 154 | @Component({ 155 | selector: 'picture', 156 | templateUrl: './picture.html' 157 | }) 158 | export class Picture { 159 | @Input() picture: any; 160 | 161 | constructor() { 162 | } 163 | } 164 | ``` 165 | 166 | Than create a template for Picture component: 167 | 168 | ```html 169 |
170 |
171 | {{ picture.title }} 172 |
173 |
174 |
{{ picture.title }}
175 |
176 |
177 | ``` 178 | 179 | We'll use this component to display a single gallery picture item. 180 | 181 | Now create `src/components/picture_upload/picture_upload.ts` file with the following content: 182 | 183 | ```typescript 184 | import { Component, Input, Output, EventEmitter } from '@angular/core'; 185 | import Cosmic from 'cosmicjs'; 186 | import { CosmicConfigService } from '../../services/cosmic_config'; 187 | 188 | @Component({ 189 | selector: 'picture-upload', 190 | templateUrl: './picture_upload.html' 191 | }) 192 | export class PictureUpload { 193 | private fl; 194 | private title; 195 | public uploading; 196 | @Output() onUpload = new EventEmitter(); 197 | 198 | constructor( 199 | private cosmicConfig: CosmicConfigService 200 | ) { 201 | this.uploading = false; 202 | this.fl = null; 203 | this.title = ""; 204 | } 205 | 206 | onFileChange(ev) { 207 | if (ev.target.files && ev.target.files.length) { 208 | this.fl = ev.target.files[0]; 209 | } 210 | } 211 | 212 | upload() { 213 | this.uploading = true; 214 | Cosmic.addMedia(this.cosmicConfig.getWriteCfg(), { 215 | media: this.fl, 216 | folder: this.fl.name 217 | }, (error, response) => { 218 | Cosmic.addObject(this.cosmicConfig.getWriteCfg(), 219 | this.cosmicConfig.buildPhotoUploadObj(this.title, response.body.media.name), 220 | (error, response) => { 221 | this.title = ''; 222 | this.fl = null; 223 | this.uploading = false; 224 | this.onUpload.emit({}); 225 | }); 226 | }); 227 | } 228 | } 229 | ``` 230 | 231 | Than add the following template: 232 | 233 | ```html 234 |
235 |
236 |
237 |
238 |
239 | 240 |
241 |
242 |
243 |
244 | 245 |
246 |
247 |
248 | 249 |
250 |
251 |
Upload is in progress
252 |
253 |
254 | ``` 255 | 256 | Add `Picture` and `PictureUpload` components to app.module.ts as it’s done with other components like AppComponent. This will allow us to use it in our app. 257 | 258 | ## What happens here? 259 | 260 | Our `Picture` component doesn't perform anything interesting - it just displays object properties. However `PuctureUpload` component doing much more interesting thing. It creates a record on CosmicJS servers, but this record has an image attached, so this makes the whole process more complicated: 261 | 262 | * upload the image to CosmicJS servers (using `addMedia` method) 263 | * obtain uploaded image name 264 | * create the new CosmicJS object (using `addObject` method) passing it obtained image name 265 | * fire an event to notify parent component about finished upload 266 | 267 | # Concatenating everything together 268 | 269 | Now we need to modify our `AppComponent` to use these newly created components. Modify `src/app/app.component.ts` to look like the following: 270 | 271 | ```typescript 272 | import { Component } from '@angular/core'; 273 | import Cosmic from 'cosmicjs'; 274 | import {CosmicConfigService} from '../services/cosmic_config'; 275 | 276 | @Component({ 277 | selector: 'app-root', 278 | templateUrl: './app.component.html', 279 | styleUrls: ['./app.component.css'] 280 | }) 281 | export class AppComponent { 282 | public items = []; 283 | page = 0; 284 | page_size = 2; 285 | scrollEnabled = true; 286 | 287 | constructor( 288 | public cosmicCfg: CosmicConfigService 289 | ) { 290 | this.reload(); 291 | } 292 | 293 | reload() { 294 | this.items = []; 295 | this.page = 0; 296 | this.scrollEnabled = true; 297 | let params = { 298 | type_slug: this.cosmicCfg.getPhotoSlug(), 299 | limit: this.page_size, 300 | skip: 0 301 | }; 302 | Cosmic.getObjectType(this.cosmicCfg.getReadCfg(), params, (err, res) => { 303 | this.items = res.objects.all; 304 | }); 305 | } 306 | 307 | onUpload() { 308 | this.reload(); 309 | } 310 | 311 | onScroll() { 312 | if (!this.scrollEnabled) { 313 | return; 314 | } 315 | this.page++; 316 | let params = { 317 | type_slug: this.cosmicCfg.getPhotoSlug(), 318 | limit: this.page_size, 319 | skip: this.page * this.page_size 320 | }; 321 | Cosmic.getObjectType(this.cosmicCfg.getReadCfg(), params, (err, res) => { 322 | if (res.objects && res.objects.all) { 323 | res.objects.all.forEach((itm) => { 324 | this.items.push(itm); 325 | }); 326 | } 327 | else { 328 | this.scrollEnabled = false; 329 | } 330 | }); 331 | } 332 | } 333 | ``` 334 | 335 | And make its template like following: 336 | 337 | ```html 338 |
339 | 340 | 341 |
342 | ``` 343 | 344 | We're planning to have the infinite-scrollable gallery (and you can already see it's directives in code). This means we have to install the right library: 345 | 346 | ```bash 347 | npm install --save ngx-infinite-scroll 348 | ``` 349 | 350 | ## What’s happening here? 351 | 352 | * until we got empty response, we're assuming there are more photos on server 353 | * we're fetching photos in bulks of 2 (`page_size` property) 354 | * we're fetching only 'photo' type objects (this is useful in case if we have more object types) 355 | * once we got empty response, we're setting the flag and stopping try to fetch more photos 356 | * once we're getting an event from `PhotoUpload` component, we're resetting the whole list. 357 | 358 | # Deploy to CosmicJS servers 359 | 360 | CosmicJS has some requirements for deploying apps: 361 | 362 | * it must be in public git repo 363 | * [Specific requirements](https://devcenter.heroku.com/) depending on your platform must be met 364 | 365 | In our case we actually have HTML5 app, so we'll need some additional software. 366 | 367 | ## Prepare config 368 | 369 | Create a `prepare.js` file in your project directory: 370 | ```javascript 371 | var fs = require('fs'); 372 | 373 | var str = ` 374 | export const environment = { 375 | production: true, 376 | write_key: '${process.env.COSMIC_WRITE_KEY}', 377 | bucket_name: '${process.env.COSMIC_BUCKET}', 378 | photos_type: 'photos' 379 | }; 380 | `; 381 | fs.writeFile("./src/environments/environment.prod.ts", str, function(err) { 382 | if(err) { 383 | return console.log(err); 384 | } 385 | console.log("The file was saved!"); 386 | }); 387 | ``` 388 | 389 | This script will rewrite default Angular production settings file to use your CosmicJS bucket write key and bucket name. 390 | 391 | ## Modify package.json 392 | 393 | Angular cli adds some packaged on `package.json` as `devDependencies`. We have to move them in `dependencies` to make our scripts work: 394 | 395 | ```json 396 | ... 397 | "dependencies": { 398 | "@angular/cli": "^1.1.3", 399 | "@angular/compiler-cli": "^4.0.0", 400 | ... 401 | }, 402 | ... 403 | ``` 404 | 405 | ## Prepare software 406 | 407 | We'll also need something to serve our Angular app. We'll use Express framework: 408 | 409 | ```bash 410 | npm install --save express 411 | ``` 412 | 413 | Add the following to your package.json: 414 | 415 | ```json 416 | { 417 | ... 418 | "scripts": { 419 | ... 420 | "start": "node app.js" 421 | }, 422 | ... 423 | "engines": { 424 | "node": "6.9.4", 425 | "npm": "4.2.0" 426 | } 427 | ... 428 | } 429 | ``` 430 | 431 | The main point is to have `start` command defined in the `scripts` section (you can safely replace default angular `start` command). This is the command which will be run to start our app. So now we have the only thing left - create the `app.js` file: 432 | 433 | ```JavaScript 434 | const express = require('express') 435 | const app = express() 436 | 437 | app.use(express.static('./dist')); 438 | 439 | app.listen(process.env.PORT, function () { 440 | }); 441 | ``` 442 | 443 | This is a simple Express app which serves `dist` dir as dir of static files. Please take note - app listens on port specified via `PORT` environment variable, it's important to run apps on CosmicJS App Server. 444 | 445 | ## Build Angular app for production 446 | 447 | We'll use `app.json` to do this (dokku `predeploy` section): 448 | 449 | ```json 450 | { 451 | "scripts": { 452 | "dokku": { 453 | "predeploy": "node prepare.js && ng build --aot --prod" 454 | } 455 | } 456 | } 457 | ``` 458 | 459 | This script will be executed before we'll launch our express app to build the Angular app for production. 460 | 461 | ## Run it! 462 | 463 | Now you can enter 'Deploy Web App' page in your CosmicJS Dashboard. 464 | 465 | ![](img-deploy.png) 466 | 467 | Simply enter your repo URL and click 'Deploy to Web' - deploy process will be started and app become ready in a couple of minutes. 468 | 469 | # Conclusion 470 | 471 | Using Cosmic JS App Server allows quickly deploy the application to hosting using a git repo and don't worry about server configuration and software installation - everything will be done by CosmicJS servers. 472 | --------------------------------------------------------------------------------