├── 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 |
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 |
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 | 
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 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/components/picture_upload/picture_upload.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 | 
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 |
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 |
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 |
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 | 
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 |
--------------------------------------------------------------------------------