├── .gitignore
├── LICENSE
├── README.md
├── angular-image-search
├── .editorconfig
├── .gitignore
├── README.md
├── angular.json
├── browserslist
├── e2e
│ ├── protractor.conf.js
│ ├── src
│ │ ├── app.e2e-spec.ts
│ │ └── app.po.ts
│ └── tsconfig.json
├── karma.conf.js
├── package-lock.json
├── package.json
├── src
│ ├── app
│ │ ├── app.component.css
│ │ ├── app.component.html
│ │ ├── app.component.spec.ts
│ │ ├── app.component.ts
│ │ ├── app.module.ts
│ │ ├── image-api.service.spec.ts
│ │ ├── image-api.service.ts
│ │ └── image-search
│ │ │ ├── image-search.component.css
│ │ │ ├── image-search.component.html
│ │ │ ├── image-search.component.spec.ts
│ │ │ └── image-search.component.ts
│ ├── assets
│ │ └── .gitkeep
│ ├── environments
│ │ ├── environment.prod.ts
│ │ └── environment.ts
│ ├── favicon.ico
│ ├── index.html
│ ├── main.ts
│ ├── now.json
│ ├── polyfills.ts
│ ├── styles.css
│ └── test.ts
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.spec.json
└── tslint.json
├── package.json
├── react-image-search
├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ ├── now.json
│ └── robots.txt
├── src
│ ├── App.css
│ ├── App.test.tsx
│ ├── App.tsx
│ ├── api.ts
│ ├── index.css
│ ├── index.tsx
│ ├── logo.svg
│ ├── react-app-env.d.ts
│ ├── serviceWorker.ts
│ └── setupTests.ts
└── tsconfig.json
├── svelte-image-search
├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
│ ├── .gitignore
│ ├── favicon.png
│ ├── global.css
│ ├── index.html
│ └── now.json
├── rollup.config.js
└── src
│ ├── App.svelte
│ ├── api.js
│ └── main.js
├── vanilla-image-search
├── .gitignore
├── README.md
├── api.js
├── app.js
├── index.html
├── now.json
└── styles.css
└── vue-image-search
├── .browserslistrc
├── .editorconfig
├── .eslintrc.js
├── .gitignore
├── README.md
├── babel.config.js
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
└── now.json
├── src
├── App.vue
├── api.js
├── assets
│ └── logo.png
└── main.js
└── vue.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # Snowpack dependency directory (https://snowpack.dev/)
45 | web_modules/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 | .parcel-cache
78 |
79 | # Next.js build output
80 | .next
81 | out
82 |
83 | # Nuxt.js build / generate output
84 | .nuxt
85 | dist
86 |
87 | # Gatsby files
88 | .cache/
89 | # Comment in the public line in if your project uses Gatsby and not Next.js
90 | # https://nextjs.org/blog/next-9-1#public-directory-support
91 | # public
92 |
93 | # vuepress build output
94 | .vuepress/dist
95 |
96 | # Serverless directories
97 | .serverless/
98 |
99 | # FuseBox cache
100 | .fusebox/
101 |
102 | # DynamoDB Local files
103 | .dynamodb/
104 |
105 | # TernJS port file
106 | .tern-port
107 |
108 | # Stores VSCode versions used for testing VSCode extensions
109 | .vscode-test
110 |
111 | # yarn v2
112 |
113 | .yarn/cache
114 | .yarn/unplugged
115 | .yarn/build-state.yml
116 | .pnp.*
117 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License Copyright (c) 2020 Coding Garden
2 |
3 | Permission is hereby granted, free
4 | of charge, to any person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use, copy, modify, merge,
7 | publish, distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to the
9 | following conditions:
10 |
11 | The above copyright notice and this permission notice
12 | (including the next paragraph) shall be included in all copies or substantial
13 | portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
18 | EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
19 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Frontend Framework Showdown 2020
2 |
3 | ## Build an Image Search App with:
4 | * Vanilla JS
5 | * React.js + Hooks
6 | * Angular 9
7 | * Vue.js + Composition API
8 | * Svelte
9 | * ???? - Some lesser known framework - We will vote!
10 |
11 | ## In Scope
12 |
13 | #### Each app will demonstrate the following concepts:
14 | * Setup / CLI Tools
15 | * User Input
16 | * DOM Events
17 | * Conditional Rendering
18 | * API Requests
19 | * Rendering Lists of Data
20 |
21 | ## Out of Scope
22 |
23 | #### Will revisit these in a future showdown:
24 | * Pagination
25 | * Testing
26 | * Routing
27 | * Global State Management
28 | * SCSS / CSS Modules
29 |
30 | ## API
31 |
32 | * All apps will use `fetch` unless the framework has something built in.
33 | * All apps will contact the Nature Image Search API:
34 | * https://nature-image-api.now.sh/search?q=
35 | * View the source code here: https://github.com/CodingGarden/nature-image-api
36 |
37 | ## Structure
38 |
39 | All apps will use the following HTML:
40 |
41 | ```html
42 |
Image Search
43 |
48 |
49 |
52 | ```
53 |
54 | ## Styles
55 |
56 | All apps will use the skeleton CSS framework:
57 |
58 | ```html
59 |
60 | ```
61 |
62 | And use these styles:
63 |
64 | ```css
65 | body {
66 | width: 80%;
67 | margin: 2em auto 0 auto;
68 | }
69 |
70 | .images {
71 | column-count: 3;
72 | }
73 |
74 | img {
75 | width: 100%;
76 | }
77 |
78 | @media (max-width: 1200px) {
79 | .images {
80 | column-count: 2;
81 | }
82 | }
83 |
84 | @media (max-width: 800px) {
85 | .images {
86 | column-count: 1;
87 | }
88 | }
89 | ```
90 |
91 | ---
92 | # TODO
93 |
94 |
95 | ## ???? - Some lesser known framework - We will vote!
96 | ---
97 |
98 | * [ ] Setup
99 | * [ ] User Input, DOM Events
100 | * [ ] Retrieve the search term from the input when the form is submitted
101 | * [ ] Conditional Rendering
102 | * [ ] Show loading image when form is submitted
103 | * [ ] API Requests
104 | * [ ] Request the images from the API with the given search term
105 | * [ ] Rendering Lists of Data
106 | * [ ] Append the API results to the page
107 | * [ ] Conditional Rendering
108 | * [ ] Hide loading image
109 |
--------------------------------------------------------------------------------
/angular-image-search/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://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 | [*.ts]
12 | quote_type = single
13 |
14 | [*.md]
15 | max_line_length = off
16 | trim_trailing_whitespace = false
17 |
--------------------------------------------------------------------------------
/angular-image-search/.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 | # Only exists if Bazel was run
8 | /bazel-out
9 |
10 | # dependencies
11 | /node_modules
12 |
13 | # profiling files
14 | chrome-profiler-events*.json
15 | speed-measure-plugin*.json
16 |
17 | # IDEs and editors
18 | /.idea
19 | .project
20 | .classpath
21 | .c9/
22 | *.launch
23 | .settings/
24 | *.sublime-workspace
25 |
26 | # IDE - VSCode
27 | .vscode/*
28 | !.vscode/settings.json
29 | !.vscode/tasks.json
30 | !.vscode/launch.json
31 | !.vscode/extensions.json
32 | .history/*
33 |
34 | # misc
35 | /.sass-cache
36 | /connect.lock
37 | /coverage
38 | /libpeerconnection.log
39 | npm-debug.log
40 | yarn-error.log
41 | testem.log
42 | /typings
43 |
44 | # System Files
45 | .DS_Store
46 | Thumbs.db
47 |
--------------------------------------------------------------------------------
/angular-image-search/README.md:
--------------------------------------------------------------------------------
1 | * [x] Setup
2 | * [x] User Input, DOM Events
3 | * [x] Retrieve the search term from the input when the form is submitted
4 | * [x] Conditional Rendering
5 | * [x] Show loading image when form is submitted
6 | * [ ] API Requests
7 | * [ ] Request the images from the API with the given search term
8 | * [ ] Rendering Lists of Data
9 | * [ ] Append the API results to the page
10 | * [ ] Conditional Rendering
11 | * [ ] Hide loading image
--------------------------------------------------------------------------------
/angular-image-search/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "newProjectRoot": "projects",
5 | "projects": {
6 | "angular-image-search": {
7 | "projectType": "application",
8 | "schematics": {},
9 | "root": "",
10 | "sourceRoot": "src",
11 | "prefix": "app",
12 | "architect": {
13 | "build": {
14 | "builder": "@angular-devkit/build-angular:browser",
15 | "options": {
16 | "outputPath": "dist/angular-image-search",
17 | "index": "src/index.html",
18 | "main": "src/main.ts",
19 | "polyfills": "src/polyfills.ts",
20 | "tsConfig": "tsconfig.app.json",
21 | "aot": true,
22 | "assets": [
23 | "src/favicon.ico",
24 | "src/assets"
25 | ],
26 | "styles": [
27 | "src/styles.css"
28 | ],
29 | "scripts": []
30 | },
31 | "configurations": {
32 | "production": {
33 | "fileReplacements": [
34 | {
35 | "replace": "src/environments/environment.ts",
36 | "with": "src/environments/environment.prod.ts"
37 | }
38 | ],
39 | "optimization": true,
40 | "outputHashing": "all",
41 | "sourceMap": false,
42 | "extractCss": true,
43 | "namedChunks": false,
44 | "extractLicenses": true,
45 | "vendorChunk": false,
46 | "buildOptimizer": true,
47 | "budgets": [
48 | {
49 | "type": "initial",
50 | "maximumWarning": "2mb",
51 | "maximumError": "5mb"
52 | },
53 | {
54 | "type": "anyComponentStyle",
55 | "maximumWarning": "6kb",
56 | "maximumError": "10kb"
57 | }
58 | ]
59 | }
60 | }
61 | },
62 | "serve": {
63 | "builder": "@angular-devkit/build-angular:dev-server",
64 | "options": {
65 | "browserTarget": "angular-image-search:build"
66 | },
67 | "configurations": {
68 | "production": {
69 | "browserTarget": "angular-image-search:build:production"
70 | }
71 | }
72 | },
73 | "extract-i18n": {
74 | "builder": "@angular-devkit/build-angular:extract-i18n",
75 | "options": {
76 | "browserTarget": "angular-image-search:build"
77 | }
78 | },
79 | "test": {
80 | "builder": "@angular-devkit/build-angular:karma",
81 | "options": {
82 | "main": "src/test.ts",
83 | "polyfills": "src/polyfills.ts",
84 | "tsConfig": "tsconfig.spec.json",
85 | "karmaConfig": "karma.conf.js",
86 | "assets": [
87 | "src/favicon.ico",
88 | "src/assets"
89 | ],
90 | "styles": [
91 | "src/styles.css"
92 | ],
93 | "scripts": []
94 | }
95 | },
96 | "lint": {
97 | "builder": "@angular-devkit/build-angular:tslint",
98 | "options": {
99 | "tsConfig": [
100 | "tsconfig.app.json",
101 | "tsconfig.spec.json",
102 | "e2e/tsconfig.json"
103 | ],
104 | "exclude": [
105 | "**/node_modules/**"
106 | ]
107 | }
108 | },
109 | "e2e": {
110 | "builder": "@angular-devkit/build-angular:protractor",
111 | "options": {
112 | "protractorConfig": "e2e/protractor.conf.js",
113 | "devServerTarget": "angular-image-search:serve"
114 | },
115 | "configurations": {
116 | "production": {
117 | "devServerTarget": "angular-image-search:serve:production"
118 | }
119 | }
120 | }
121 | }
122 | }},
123 | "defaultProject": "angular-image-search"
124 | }
125 |
--------------------------------------------------------------------------------
/angular-image-search/browserslist:
--------------------------------------------------------------------------------
1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
2 | # For additional information regarding the format and rule options, please see:
3 | # https://github.com/browserslist/browserslist#queries
4 |
5 | # You can see what browsers were selected by your queries by running:
6 | # npx browserslist
7 |
8 | > 0.5%
9 | last 2 versions
10 | Firefox ESR
11 | not dead
12 | not IE 9-11 # For IE 9-11 support, remove 'not'.
--------------------------------------------------------------------------------
/angular-image-search/e2e/protractor.conf.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | // Protractor configuration file, see link for more information
3 | // https://github.com/angular/protractor/blob/master/lib/config.ts
4 |
5 | const { SpecReporter } = require('jasmine-spec-reporter');
6 |
7 | /**
8 | * @type { import("protractor").Config }
9 | */
10 | exports.config = {
11 | allScriptsTimeout: 11000,
12 | specs: [
13 | './src/**/*.e2e-spec.ts'
14 | ],
15 | capabilities: {
16 | browserName: 'chrome'
17 | },
18 | directConnect: true,
19 | baseUrl: 'http://localhost:4200/',
20 | framework: 'jasmine',
21 | jasmineNodeOpts: {
22 | showColors: true,
23 | defaultTimeoutInterval: 30000,
24 | print: function() {}
25 | },
26 | onPrepare() {
27 | require('ts-node').register({
28 | project: require('path').join(__dirname, './tsconfig.json')
29 | });
30 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
31 | }
32 | };
--------------------------------------------------------------------------------
/angular-image-search/e2e/src/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { AppPage } from './app.po';
2 | import { browser, logging } from 'protractor';
3 |
4 | describe('workspace-project App', () => {
5 | let page: AppPage;
6 |
7 | beforeEach(() => {
8 | page = new AppPage();
9 | });
10 |
11 | it('should display welcome message', () => {
12 | page.navigateTo();
13 | expect(page.getTitleText()).toEqual('angular-image-search app is running!');
14 | });
15 |
16 | afterEach(async () => {
17 | // Assert that there are no errors emitted from the browser
18 | const logs = await browser.manage().logs().get(logging.Type.BROWSER);
19 | expect(logs).not.toContain(jasmine.objectContaining({
20 | level: logging.Level.SEVERE,
21 | } as logging.Entry));
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/angular-image-search/e2e/src/app.po.ts:
--------------------------------------------------------------------------------
1 | import { browser, by, element } from 'protractor';
2 |
3 | export class AppPage {
4 | navigateTo(): Promise {
5 | return browser.get(browser.baseUrl) as Promise;
6 | }
7 |
8 | getTitleText(): Promise {
9 | return element(by.css('app-root .content span')).getText() as Promise;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/angular-image-search/e2e/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/e2e",
5 | "module": "commonjs",
6 | "target": "es5",
7 | "types": [
8 | "jasmine",
9 | "jasminewd2",
10 | "node"
11 | ]
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/angular-image-search/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/1.0/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular-devkit/build-angular'],
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-devkit/build-angular/plugins/karma')
14 | ],
15 | client: {
16 | clearContext: false // leave Jasmine Spec Runner output visible in browser
17 | },
18 | coverageIstanbulReporter: {
19 | dir: require('path').join(__dirname, './coverage/angular-image-search'),
20 | reports: ['html', 'lcovonly', 'text-summary'],
21 | fixWebpackSourcePaths: true
22 | },
23 | reporters: ['progress', 'kjhtml'],
24 | port: 9876,
25 | colors: true,
26 | logLevel: config.LOG_INFO,
27 | autoWatch: true,
28 | browsers: ['Chrome'],
29 | singleRun: false,
30 | restartOnFileChange: true
31 | });
32 | };
33 |
--------------------------------------------------------------------------------
/angular-image-search/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-image-search",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "ng": "ng",
6 | "start": "ng serve",
7 | "build": "ng build",
8 | "test": "ng test",
9 | "lint": "ng lint",
10 | "e2e": "ng e2e"
11 | },
12 | "private": true,
13 | "dependencies": {
14 | "@angular/animations": "~9.1.6",
15 | "@angular/common": "~9.1.6",
16 | "@angular/compiler": "~9.1.6",
17 | "@angular/core": "~9.1.6",
18 | "@angular/forms": "~9.1.6",
19 | "@angular/platform-browser": "~9.1.6",
20 | "@angular/platform-browser-dynamic": "~9.1.6",
21 | "@angular/router": "~9.1.6",
22 | "rxjs": "~6.5.4",
23 | "tslib": "^1.10.0",
24 | "zone.js": "~0.10.2"
25 | },
26 | "devDependencies": {
27 | "@angular-devkit/build-angular": "^0.1000.8",
28 | "@angular/cli": "~9.1.5",
29 | "@angular/compiler-cli": "~9.1.6",
30 | "@types/jasmine": "~3.5.0",
31 | "@types/jasminewd2": "~2.0.3",
32 | "@types/node": "^12.11.1",
33 | "codelyzer": "^5.1.2",
34 | "jasmine-core": "~3.5.0",
35 | "jasmine-spec-reporter": "~4.2.1",
36 | "karma": "~5.0.0",
37 | "karma-chrome-launcher": "~3.1.0",
38 | "karma-coverage-istanbul-reporter": "~2.1.0",
39 | "karma-jasmine": "~3.0.1",
40 | "karma-jasmine-html-reporter": "^1.4.2",
41 | "protractor": "^7.0.0",
42 | "ts-node": "~8.3.0",
43 | "tslint": "~6.1.0",
44 | "typescript": "~3.8.3"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/angular-image-search/src/app/app.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodingGarden/frontend-framework-showdown-2020/bc113954b3114e0b2c6ae73b4761b816908e0e95/angular-image-search/src/app/app.component.css
--------------------------------------------------------------------------------
/angular-image-search/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/angular-image-search/src/app/app.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed, async } from '@angular/core/testing';
2 | import { AppComponent } from './app.component';
3 |
4 | describe('AppComponent', () => {
5 | beforeEach(async(() => {
6 | TestBed.configureTestingModule({
7 | declarations: [
8 | AppComponent
9 | ],
10 | }).compileComponents();
11 | }));
12 |
13 | it('should create the app', () => {
14 | const fixture = TestBed.createComponent(AppComponent);
15 | const app = fixture.componentInstance;
16 | expect(app).toBeTruthy();
17 | });
18 |
19 | it(`should have as title 'angular-image-search'`, () => {
20 | const fixture = TestBed.createComponent(AppComponent);
21 | const app = fixture.componentInstance;
22 | expect(app.title).toEqual('angular-image-search');
23 | });
24 |
25 | it('should render title', () => {
26 | const fixture = TestBed.createComponent(AppComponent);
27 | fixture.detectChanges();
28 | const compiled = fixture.nativeElement;
29 | expect(compiled.querySelector('.content span').textContent).toContain('angular-image-search app is running!');
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/angular-image-search/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-root',
5 | templateUrl: './app.component.html',
6 | styleUrls: ['./app.component.css']
7 | })
8 | export class AppComponent {
9 | title = 'angular-image-search';
10 | }
11 |
--------------------------------------------------------------------------------
/angular-image-search/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { BrowserModule } from '@angular/platform-browser';
2 | import { NgModule } from '@angular/core';
3 | import { FormsModule } from '@angular/forms';
4 | import { HttpClientModule } from '@angular/common/http'
5 |
6 | import { AppComponent } from './app.component';
7 | import { ImageSearchComponent } from './image-search/image-search.component';
8 |
9 | @NgModule({
10 | declarations: [
11 | AppComponent,
12 | ImageSearchComponent
13 | ],
14 | imports: [
15 | BrowserModule,
16 | FormsModule,
17 | HttpClientModule
18 | ],
19 | providers: [],
20 | bootstrap: [AppComponent]
21 | })
22 | export class AppModule { }
23 |
--------------------------------------------------------------------------------
/angular-image-search/src/app/image-api.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { ImageApiService } from './image-api.service';
4 |
5 | describe('ImageApiService', () => {
6 | let service: ImageApiService;
7 |
8 | beforeEach(() => {
9 | TestBed.configureTestingModule({});
10 | service = TestBed.inject(ImageApiService);
11 | });
12 |
13 | it('should be created', () => {
14 | expect(service).toBeTruthy();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/angular-image-search/src/app/image-api.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { HttpClient } from '@angular/common/http';
3 | import { Observable } from 'rxjs';
4 | import { map } from 'rxjs/operators';
5 |
6 | const API_URL = 'https://nature-image-api.now.sh/search?q=';
7 |
8 | export interface ImageResult {
9 | image: string;
10 | }
11 |
12 | export interface APIResult {
13 | images: ImageResult[];
14 | }
15 |
16 | @Injectable({
17 | providedIn: 'root'
18 | })
19 | export class ImageApiService {
20 |
21 | constructor(private http: HttpClient) { }
22 |
23 | getImages(searchTerm): Observable {
24 | return this.http.get(`${API_URL}${searchTerm}`)
25 | .pipe(map(({ images }) => images.map(({ image }: ImageResult) => image)));
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/angular-image-search/src/app/image-search/image-search.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodingGarden/frontend-framework-showdown-2020/bc113954b3114e0b2c6ae73b4761b816908e0e95/angular-image-search/src/app/image-search/image-search.component.css
--------------------------------------------------------------------------------
/angular-image-search/src/app/image-search/image-search.component.html:
--------------------------------------------------------------------------------
1 | Angular Image Search
2 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/angular-image-search/src/app/image-search/image-search.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { ImageSearchComponent } from './image-search.component';
4 |
5 | describe('ImageSearchComponent', () => {
6 | let component: ImageSearchComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ ImageSearchComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(ImageSearchComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/angular-image-search/src/app/image-search/image-search.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { ImageApiService, APIResult, ImageResult } from '../image-api.service';
3 |
4 | @Component({
5 | selector: 'app-image-search',
6 | templateUrl: './image-search.component.html',
7 | styleUrls: ['./image-search.component.css']
8 | })
9 | export class ImageSearchComponent implements OnInit {
10 | searchTerm = '';
11 | loading = false;
12 | images: string[] = [];
13 |
14 | constructor(private imageAPIService: ImageApiService) { }
15 |
16 | ngOnInit(): void {}
17 |
18 | formSubmitted() {
19 | this.images = [];
20 | this.loading = true;
21 | this.imageAPIService.getImages(this.searchTerm)
22 | .subscribe((images: string[]) => {
23 | this.images = images;
24 | this.loading = false;
25 | });
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/angular-image-search/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodingGarden/frontend-framework-showdown-2020/bc113954b3114e0b2c6ae73b4761b816908e0e95/angular-image-search/src/assets/.gitkeep
--------------------------------------------------------------------------------
/angular-image-search/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true
3 | };
4 |
--------------------------------------------------------------------------------
/angular-image-search/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // This file can be replaced during build by using the `fileReplacements` array.
2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
3 | // The list of file replacements can be found in `angular.json`.
4 |
5 | export const environment = {
6 | production: false
7 | };
8 |
9 | /*
10 | * For easier debugging in development mode, you can import the following file
11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
12 | *
13 | * This import should be commented out in production mode because it will have a negative impact
14 | * on performance if an error is thrown.
15 | */
16 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI.
17 |
--------------------------------------------------------------------------------
/angular-image-search/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodingGarden/frontend-framework-showdown-2020/bc113954b3114e0b2c6ae73b4761b816908e0e95/angular-image-search/src/favicon.ico
--------------------------------------------------------------------------------
/angular-image-search/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Angular Image Search
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/angular-image-search/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 | .catch(err => console.error(err));
13 |
--------------------------------------------------------------------------------
/angular-image-search/src/now.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 2,
3 | "builds": [
4 | {
5 | "src": "*",
6 | "use": "@now/static"
7 | }
8 | ],
9 | "alias": [
10 | "angular-image-search"
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/angular-image-search/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/guide/browser-support
15 | */
16 |
17 | /***************************************************************************************************
18 | * BROWSER POLYFILLS
19 | */
20 |
21 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */
22 | // import 'classlist.js'; // Run `npm install --save classlist.js`.
23 |
24 | /**
25 | * Web Animations `@angular/platform-browser/animations`
26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
28 | */
29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`.
30 |
31 | /**
32 | * By default, zone.js will patch all possible macroTask and DomEvents
33 | * user can disable parts of macroTask/DomEvents patch by setting following flags
34 | * because those flags need to be set before `zone.js` being loaded, and webpack
35 | * will put import in the top of bundle, so user need to create a separate file
36 | * in this directory (for example: zone-flags.ts), and put the following flags
37 | * into that file, and then add the following code before importing zone.js.
38 | * import './zone-flags';
39 | *
40 | * The flags allowed in zone-flags.ts are listed here.
41 | *
42 | * The following flags will work for all browsers.
43 | *
44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
46 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
47 | *
48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge
50 | *
51 | * (window as any).__Zone_enable_cross_context_check = true;
52 | *
53 | */
54 |
55 | /***************************************************************************************************
56 | * Zone JS is required by default for Angular itself.
57 | */
58 | import 'zone.js/dist/zone'; // Included with Angular CLI.
59 |
60 |
61 | /***************************************************************************************************
62 | * APPLICATION IMPORTS
63 | */
64 |
--------------------------------------------------------------------------------
/angular-image-search/src/styles.css:
--------------------------------------------------------------------------------
1 | body {
2 | width: 80%;
3 | margin: 2em auto 0 auto;
4 | }
5 |
6 | .images {
7 | column-count: 3;
8 | }
9 |
10 | img {
11 | width: 100%;
12 | }
13 |
14 | @media (max-width: 1200px) {
15 | .images {
16 | column-count: 2;
17 | }
18 | }
19 |
20 | @media (max-width: 800px) {
21 | .images {
22 | column-count: 1;
23 | }
24 | }
--------------------------------------------------------------------------------
/angular-image-search/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/zone-testing';
4 | import { getTestBed } from '@angular/core/testing';
5 | import {
6 | BrowserDynamicTestingModule,
7 | platformBrowserDynamicTesting
8 | } from '@angular/platform-browser-dynamic/testing';
9 |
10 | declare const require: {
11 | context(path: string, deep?: boolean, filter?: RegExp): {
12 | keys(): string[];
13 | (id: string): T;
14 | };
15 | };
16 |
17 | // First, initialize the Angular testing environment.
18 | getTestBed().initTestEnvironment(
19 | BrowserDynamicTestingModule,
20 | platformBrowserDynamicTesting()
21 | );
22 | // Then we find all the tests.
23 | const context = require.context('./', true, /\.spec\.ts$/);
24 | // And load the modules.
25 | context.keys().map(context);
26 |
--------------------------------------------------------------------------------
/angular-image-search/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "./out-tsc/app",
5 | "types": []
6 | },
7 | "files": [
8 | "src/main.ts",
9 | "src/polyfills.ts"
10 | ],
11 | "include": [
12 | "src/**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/angular-image-search/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "baseUrl": "./",
5 | "outDir": "./dist/out-tsc",
6 | "sourceMap": true,
7 | "declaration": false,
8 | "downlevelIteration": true,
9 | "experimentalDecorators": true,
10 | "module": "esnext",
11 | "moduleResolution": "node",
12 | "importHelpers": true,
13 | "target": "es2015",
14 | "lib": [
15 | "es2018",
16 | "dom"
17 | ]
18 | },
19 | "angularCompilerOptions": {
20 | "fullTemplateTypeCheck": true,
21 | "strictInjectionParameters": true
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/angular-image-search/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "./out-tsc/spec",
5 | "types": [
6 | "jasmine",
7 | "node"
8 | ]
9 | },
10 | "files": [
11 | "src/test.ts",
12 | "src/polyfills.ts"
13 | ],
14 | "include": [
15 | "src/**/*.spec.ts",
16 | "src/**/*.d.ts"
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/angular-image-search/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "tslint:recommended",
3 | "rules": {
4 | "align": {
5 | "options": [
6 | "parameters",
7 | "statements"
8 | ]
9 | },
10 | "array-type": false,
11 | "arrow-return-shorthand": true,
12 | "curly": true,
13 | "deprecation": {
14 | "severity": "warning"
15 | },
16 | "component-class-suffix": true,
17 | "contextual-lifecycle": true,
18 | "directive-class-suffix": true,
19 | "directive-selector": [
20 | true,
21 | "attribute",
22 | "app",
23 | "camelCase"
24 | ],
25 | "component-selector": [
26 | true,
27 | "element",
28 | "app",
29 | "kebab-case"
30 | ],
31 | "eofline": true,
32 | "import-blacklist": [
33 | true,
34 | "rxjs/Rx"
35 | ],
36 | "import-spacing": true,
37 | "indent": {
38 | "options": [
39 | "spaces"
40 | ]
41 | },
42 | "max-classes-per-file": false,
43 | "max-line-length": [
44 | true,
45 | 140
46 | ],
47 | "member-ordering": [
48 | true,
49 | {
50 | "order": [
51 | "static-field",
52 | "instance-field",
53 | "static-method",
54 | "instance-method"
55 | ]
56 | }
57 | ],
58 | "no-console": [
59 | true,
60 | "debug",
61 | "info",
62 | "time",
63 | "timeEnd",
64 | "trace"
65 | ],
66 | "no-empty": false,
67 | "no-inferrable-types": [
68 | true,
69 | "ignore-params"
70 | ],
71 | "no-non-null-assertion": true,
72 | "no-redundant-jsdoc": true,
73 | "no-switch-case-fall-through": true,
74 | "no-var-requires": false,
75 | "object-literal-key-quotes": [
76 | true,
77 | "as-needed"
78 | ],
79 | "quotemark": [
80 | true,
81 | "single"
82 | ],
83 | "semicolon": {
84 | "options": [
85 | "always"
86 | ]
87 | },
88 | "space-before-function-paren": {
89 | "options": {
90 | "anonymous": "never",
91 | "asyncArrow": "always",
92 | "constructor": "never",
93 | "method": "never",
94 | "named": "never"
95 | }
96 | },
97 | "typedef-whitespace": {
98 | "options": [
99 | {
100 | "call-signature": "nospace",
101 | "index-signature": "nospace",
102 | "parameter": "nospace",
103 | "property-declaration": "nospace",
104 | "variable-declaration": "nospace"
105 | },
106 | {
107 | "call-signature": "onespace",
108 | "index-signature": "onespace",
109 | "parameter": "onespace",
110 | "property-declaration": "onespace",
111 | "variable-declaration": "onespace"
112 | }
113 | ]
114 | },
115 | "variable-name": {
116 | "options": [
117 | "ban-keywords",
118 | "check-format",
119 | "allow-pascal-case"
120 | ]
121 | },
122 | "whitespace": {
123 | "options": [
124 | "check-branch",
125 | "check-decl",
126 | "check-operator",
127 | "check-separator",
128 | "check-type",
129 | "check-typecast"
130 | ]
131 | },
132 | "no-conflicting-lifecycle": true,
133 | "no-host-metadata-property": true,
134 | "no-input-rename": true,
135 | "no-inputs-metadata-property": true,
136 | "no-output-native": true,
137 | "no-output-on-prefix": true,
138 | "no-output-rename": true,
139 | "no-outputs-metadata-property": true,
140 | "template-banana-in-box": true,
141 | "template-no-negated-async": true,
142 | "use-lifecycle-interface": true,
143 | "use-pipe-transform-interface": true
144 | },
145 | "rulesDirectory": [
146 | "codelyzer"
147 | ]
148 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend-framework-showdown-2020",
3 | "version": "1.0.0",
4 | "description": "Build an Image Search App with several popular frontend frameworks",
5 | "main": "README.md",
6 | "author": "CJ R. (https://w3cj.sh)",
7 | "license": "MIT"
8 | }
--------------------------------------------------------------------------------
/react-image-search/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/react-image-search/README.md:
--------------------------------------------------------------------------------
1 | * [x] Setup
2 | * [x] User Input, DOM Events
3 | * [x] Retrieve the search term from the input when the form is submitted
4 | * [x] Conditional Rendering
5 | * [x] Show loading image when form is submitted
6 | * [x] API Requests
7 | * [x] Request the images from the API with the given search term
8 | * [x] Rendering Lists of Data
9 | * [x] Append the API results to the page
10 | * [x] Conditional Rendering
11 | * [x] Hide loading image
--------------------------------------------------------------------------------
/react-image-search/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-image-search",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.11.4",
7 | "@testing-library/react": "^10.4.9",
8 | "@testing-library/user-event": "^12.1.3",
9 | "@types/jest": "^26.0.10",
10 | "@types/node": "^14.6.1",
11 | "@types/react": "^16.9.48",
12 | "@types/react-dom": "^16.9.8",
13 | "react": "^16.13.1",
14 | "react-dom": "^16.13.1",
15 | "react-scripts": "3.4.3",
16 | "typescript": "^4.0.2"
17 | },
18 | "scripts": {
19 | "start": "react-scripts start",
20 | "build": "react-scripts build",
21 | "test": "react-scripts test",
22 | "eject": "react-scripts eject"
23 | },
24 | "eslintConfig": {
25 | "extends": "react-app"
26 | },
27 | "browserslist": {
28 | "production": [
29 | ">0.2%",
30 | "not dead",
31 | "not op_mini all"
32 | ],
33 | "development": [
34 | "last 1 chrome version",
35 | "last 1 firefox version",
36 | "last 1 safari version"
37 | ]
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/react-image-search/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodingGarden/frontend-framework-showdown-2020/bc113954b3114e0b2c6ae73b4761b816908e0e95/react-image-search/public/favicon.ico
--------------------------------------------------------------------------------
/react-image-search/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
33 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/react-image-search/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodingGarden/frontend-framework-showdown-2020/bc113954b3114e0b2c6ae73b4761b816908e0e95/react-image-search/public/logo192.png
--------------------------------------------------------------------------------
/react-image-search/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodingGarden/frontend-framework-showdown-2020/bc113954b3114e0b2c6ae73b4761b816908e0e95/react-image-search/public/logo512.png
--------------------------------------------------------------------------------
/react-image-search/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/react-image-search/public/now.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 2,
3 | "builds": [
4 | {
5 | "src": "*",
6 | "use": "@now/static"
7 | }
8 | ],
9 | "alias": [
10 | "react-image-search"
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/react-image-search/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/react-image-search/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/react-image-search/src/App.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import App from './App';
4 |
5 | test('renders learn react link', () => {
6 | const { getByText } = render();
7 | const linkElement = getByText(/learn react/i);
8 | expect(linkElement).toBeInTheDocument();
9 | });
10 |
--------------------------------------------------------------------------------
/react-image-search/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useCallback } from "react";
2 |
3 | import getImages from "./api";
4 |
5 | const App: React.FC = () => {
6 | const [title] = useState("React Image Search");
7 | const [searchTerm, setSearchTerm] = useState("");
8 | const [loading, setLoading] = useState(false);
9 | const [images, setImages] = useState([]);
10 |
11 | const formSubmitted = useCallback(async (event: React.SyntheticEvent): Promise => {
12 | event.preventDefault();
13 | setImages([]);
14 | setLoading(true);
15 | const images = await getImages(searchTerm);
16 | setImages(images);
17 | setLoading(false);
18 | }, [searchTerm]);
19 |
20 | const onSearchTermChanged = useCallback((event: React.ChangeEvent): void => {
21 | setSearchTerm(event.target.value);
22 | }, []);
23 |
24 | return (
25 |
26 | {title}
27 |
39 | {loading && (
40 |
45 | )}
46 | {/* {loading ? (
47 |
52 | ) : null} */}
53 |
54 | {images.map(url => (
55 |
56 | ))}
57 |
58 |
59 | );
60 | };
61 |
62 | export default App;
63 |
--------------------------------------------------------------------------------
/react-image-search/src/api.ts:
--------------------------------------------------------------------------------
1 | const API_URL = `https://nature-image-api.now.sh/search?q=`;
2 |
3 | interface ImageResult {
4 | image: string;
5 | }
6 |
7 | export default async function getImages(searchTerm: string): Promise {
8 | const response = await fetch(`${API_URL}${searchTerm}`);
9 | const json = await response.json();
10 | return json.images.map(({ image }: ImageResult) => image);
11 | }
--------------------------------------------------------------------------------
/react-image-search/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | width: 80%;
3 | margin: 2em auto 0 auto;
4 | }
5 |
6 | .images {
7 | column-count: 3;
8 | }
9 |
10 | img {
11 | width: 100%;
12 | }
13 |
14 | @media (max-width: 1200px) {
15 | .images {
16 | column-count: 2;
17 | }
18 | }
19 |
20 | @media (max-width: 800px) {
21 | .images {
22 | column-count: 1;
23 | }
24 | }
--------------------------------------------------------------------------------
/react-image-search/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import * as serviceWorker from './serviceWorker';
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById('root')
12 | );
13 |
14 | // If you want your app to work offline and load faster, you can change
15 | // unregister() to register() below. Note this comes with some pitfalls.
16 | // Learn more about service workers: https://bit.ly/CRA-PWA
17 | serviceWorker.unregister();
18 |
--------------------------------------------------------------------------------
/react-image-search/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/react-image-search/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/react-image-search/src/serviceWorker.ts:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.0/8 are considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | type Config = {
24 | onSuccess?: (registration: ServiceWorkerRegistration) => void;
25 | onUpdate?: (registration: ServiceWorkerRegistration) => void;
26 | };
27 |
28 | export function register(config?: Config) {
29 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
30 | // The URL constructor is available in all browsers that support SW.
31 | const publicUrl = new URL(
32 | process.env.PUBLIC_URL,
33 | window.location.href
34 | );
35 | if (publicUrl.origin !== window.location.origin) {
36 | // Our service worker won't work if PUBLIC_URL is on a different origin
37 | // from what our page is served on. This might happen if a CDN is used to
38 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
39 | return;
40 | }
41 |
42 | window.addEventListener('load', () => {
43 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
44 |
45 | if (isLocalhost) {
46 | // This is running on localhost. Let's check if a service worker still exists or not.
47 | checkValidServiceWorker(swUrl, config);
48 |
49 | // Add some additional logging to localhost, pointing developers to the
50 | // service worker/PWA documentation.
51 | navigator.serviceWorker.ready.then(() => {
52 | console.log(
53 | 'This web app is being served cache-first by a service ' +
54 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
55 | );
56 | });
57 | } else {
58 | // Is not localhost. Just register service worker
59 | registerValidSW(swUrl, config);
60 | }
61 | });
62 | }
63 | }
64 |
65 | function registerValidSW(swUrl: string, config?: Config) {
66 | navigator.serviceWorker
67 | .register(swUrl)
68 | .then(registration => {
69 | registration.onupdatefound = () => {
70 | const installingWorker = registration.installing;
71 | if (installingWorker == null) {
72 | return;
73 | }
74 | installingWorker.onstatechange = () => {
75 | if (installingWorker.state === 'installed') {
76 | if (navigator.serviceWorker.controller) {
77 | // At this point, the updated precached content has been fetched,
78 | // but the previous service worker will still serve the older
79 | // content until all client tabs are closed.
80 | console.log(
81 | 'New content is available and will be used when all ' +
82 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
83 | );
84 |
85 | // Execute callback
86 | if (config && config.onUpdate) {
87 | config.onUpdate(registration);
88 | }
89 | } else {
90 | // At this point, everything has been precached.
91 | // It's the perfect time to display a
92 | // "Content is cached for offline use." message.
93 | console.log('Content is cached for offline use.');
94 |
95 | // Execute callback
96 | if (config && config.onSuccess) {
97 | config.onSuccess(registration);
98 | }
99 | }
100 | }
101 | };
102 | };
103 | })
104 | .catch(error => {
105 | console.error('Error during service worker registration:', error);
106 | });
107 | }
108 |
109 | function checkValidServiceWorker(swUrl: string, config?: Config) {
110 | // Check if the service worker can be found. If it can't reload the page.
111 | fetch(swUrl, {
112 | headers: { 'Service-Worker': 'script' }
113 | })
114 | .then(response => {
115 | // Ensure service worker exists, and that we really are getting a JS file.
116 | const contentType = response.headers.get('content-type');
117 | if (
118 | response.status === 404 ||
119 | (contentType != null && contentType.indexOf('javascript') === -1)
120 | ) {
121 | // No service worker found. Probably a different app. Reload the page.
122 | navigator.serviceWorker.ready.then(registration => {
123 | registration.unregister().then(() => {
124 | window.location.reload();
125 | });
126 | });
127 | } else {
128 | // Service worker found. Proceed as normal.
129 | registerValidSW(swUrl, config);
130 | }
131 | })
132 | .catch(() => {
133 | console.log(
134 | 'No internet connection found. App is running in offline mode.'
135 | );
136 | });
137 | }
138 |
139 | export function unregister() {
140 | if ('serviceWorker' in navigator) {
141 | navigator.serviceWorker.ready
142 | .then(registration => {
143 | registration.unregister();
144 | })
145 | .catch(error => {
146 | console.error(error.message);
147 | });
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/react-image-search/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom/extend-expect';
6 |
--------------------------------------------------------------------------------
/react-image-search/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "module": "esnext",
16 | "moduleResolution": "node",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "noEmit": true,
20 | "jsx": "react"
21 | },
22 | "include": [
23 | "src"
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/svelte-image-search/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /public/build/
3 |
4 | .DS_Store
5 |
--------------------------------------------------------------------------------
/svelte-image-search/README.md:
--------------------------------------------------------------------------------
1 | * [x] Setup
2 | * [x] User Input, DOM Events
3 | * [x] Retrieve the search term from the input when the form is submitted
4 | * [x] Conditional Rendering
5 | * [x] Show loading image when form is submitted
6 | * [x] API Requests
7 | * [x] Request the images from the API with the given search term
8 | * [x] Rendering Lists of Data
9 | * [x] Append the API results to the page
10 | * [x] Conditional Rendering
11 | * [x] Hide loading image
--------------------------------------------------------------------------------
/svelte-image-search/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "svelte-app",
3 | "version": "1.0.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "@babel/code-frame": {
8 | "version": "7.10.4",
9 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz",
10 | "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==",
11 | "dev": true,
12 | "requires": {
13 | "@babel/highlight": "^7.10.4"
14 | }
15 | },
16 | "@babel/helper-validator-identifier": {
17 | "version": "7.10.4",
18 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz",
19 | "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==",
20 | "dev": true
21 | },
22 | "@babel/highlight": {
23 | "version": "7.10.4",
24 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz",
25 | "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==",
26 | "dev": true,
27 | "requires": {
28 | "@babel/helper-validator-identifier": "^7.10.4",
29 | "chalk": "^2.0.0",
30 | "js-tokens": "^4.0.0"
31 | }
32 | },
33 | "@polka/url": {
34 | "version": "1.0.0-next.11",
35 | "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.11.tgz",
36 | "integrity": "sha512-3NsZsJIA/22P3QUyrEDNA2D133H4j224twJrdipXN38dpnIOzAbUDtOwkcJ5pXmn75w7LSQDjA4tO9dm1XlqlA=="
37 | },
38 | "@rollup/plugin-commonjs": {
39 | "version": "15.0.0",
40 | "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-15.0.0.tgz",
41 | "integrity": "sha512-8uAdikHqVyrT32w1zB9VhW6uGwGjhKgnDNP4pQJsjdnyF4FgCj6/bmv24c7v2CuKhq32CcyCwRzMPEElaKkn0w==",
42 | "dev": true,
43 | "requires": {
44 | "@rollup/pluginutils": "^3.1.0",
45 | "commondir": "^1.0.1",
46 | "estree-walker": "^2.0.1",
47 | "glob": "^7.1.6",
48 | "is-reference": "^1.2.1",
49 | "magic-string": "^0.25.7",
50 | "resolve": "^1.17.0"
51 | }
52 | },
53 | "@rollup/plugin-node-resolve": {
54 | "version": "9.0.0",
55 | "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-9.0.0.tgz",
56 | "integrity": "sha512-gPz+utFHLRrd41WMP13Jq5mqqzHL3OXrfj3/MkSyB6UBIcuNt9j60GCbarzMzdf1VHFpOxfQh/ez7wyadLMqkg==",
57 | "dev": true,
58 | "requires": {
59 | "@rollup/pluginutils": "^3.1.0",
60 | "@types/resolve": "1.17.1",
61 | "builtin-modules": "^3.1.0",
62 | "deepmerge": "^4.2.2",
63 | "is-module": "^1.0.0",
64 | "resolve": "^1.17.0"
65 | }
66 | },
67 | "@rollup/pluginutils": {
68 | "version": "3.1.0",
69 | "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz",
70 | "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==",
71 | "dev": true,
72 | "requires": {
73 | "@types/estree": "0.0.39",
74 | "estree-walker": "^1.0.1",
75 | "picomatch": "^2.2.2"
76 | },
77 | "dependencies": {
78 | "estree-walker": {
79 | "version": "1.0.1",
80 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz",
81 | "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==",
82 | "dev": true
83 | }
84 | }
85 | },
86 | "@types/estree": {
87 | "version": "0.0.39",
88 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
89 | "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==",
90 | "dev": true
91 | },
92 | "@types/node": {
93 | "version": "14.6.1",
94 | "resolved": "https://registry.npmjs.org/@types/node/-/node-14.6.1.tgz",
95 | "integrity": "sha512-HnYlg/BRF8uC1FyKRFZwRaCPTPYKa+6I8QiUZFLredaGOou481cgFS4wKRFyKvQtX8xudqkSdBczJHIYSQYKrQ==",
96 | "dev": true
97 | },
98 | "@types/resolve": {
99 | "version": "1.17.1",
100 | "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
101 | "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==",
102 | "dev": true,
103 | "requires": {
104 | "@types/node": "*"
105 | }
106 | },
107 | "ansi-styles": {
108 | "version": "3.2.1",
109 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
110 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
111 | "dev": true,
112 | "requires": {
113 | "color-convert": "^1.9.0"
114 | }
115 | },
116 | "anymatch": {
117 | "version": "3.1.1",
118 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz",
119 | "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==",
120 | "dev": true,
121 | "requires": {
122 | "normalize-path": "^3.0.0",
123 | "picomatch": "^2.0.4"
124 | }
125 | },
126 | "async-limiter": {
127 | "version": "1.0.1",
128 | "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
129 | "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==",
130 | "dev": true
131 | },
132 | "balanced-match": {
133 | "version": "1.0.0",
134 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
135 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
136 | "dev": true
137 | },
138 | "binary-extensions": {
139 | "version": "2.0.0",
140 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz",
141 | "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==",
142 | "dev": true
143 | },
144 | "brace-expansion": {
145 | "version": "1.1.11",
146 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
147 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
148 | "dev": true,
149 | "requires": {
150 | "balanced-match": "^1.0.0",
151 | "concat-map": "0.0.1"
152 | }
153 | },
154 | "braces": {
155 | "version": "3.0.2",
156 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
157 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
158 | "dev": true,
159 | "requires": {
160 | "fill-range": "^7.0.1"
161 | }
162 | },
163 | "buffer-from": {
164 | "version": "1.1.1",
165 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
166 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
167 | "dev": true
168 | },
169 | "builtin-modules": {
170 | "version": "3.1.0",
171 | "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.1.0.tgz",
172 | "integrity": "sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==",
173 | "dev": true
174 | },
175 | "chalk": {
176 | "version": "2.4.2",
177 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
178 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
179 | "dev": true,
180 | "requires": {
181 | "ansi-styles": "^3.2.1",
182 | "escape-string-regexp": "^1.0.5",
183 | "supports-color": "^5.3.0"
184 | }
185 | },
186 | "chokidar": {
187 | "version": "3.4.0",
188 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.0.tgz",
189 | "integrity": "sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ==",
190 | "dev": true,
191 | "requires": {
192 | "anymatch": "~3.1.1",
193 | "braces": "~3.0.2",
194 | "fsevents": "~2.1.2",
195 | "glob-parent": "~5.1.0",
196 | "is-binary-path": "~2.1.0",
197 | "is-glob": "~4.0.1",
198 | "normalize-path": "~3.0.0",
199 | "readdirp": "~3.4.0"
200 | }
201 | },
202 | "color-convert": {
203 | "version": "1.9.3",
204 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
205 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
206 | "dev": true,
207 | "requires": {
208 | "color-name": "1.1.3"
209 | }
210 | },
211 | "color-name": {
212 | "version": "1.1.3",
213 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
214 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
215 | "dev": true
216 | },
217 | "commander": {
218 | "version": "2.20.3",
219 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
220 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
221 | "dev": true
222 | },
223 | "commondir": {
224 | "version": "1.0.1",
225 | "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
226 | "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
227 | "dev": true
228 | },
229 | "concat-map": {
230 | "version": "0.0.1",
231 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
232 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
233 | "dev": true
234 | },
235 | "console-clear": {
236 | "version": "1.1.1",
237 | "resolved": "https://registry.npmjs.org/console-clear/-/console-clear-1.1.1.tgz",
238 | "integrity": "sha512-pMD+MVR538ipqkG5JXeOEbKWS5um1H4LUUccUQG68qpeqBYbzYy79Gh55jkd2TtPdRfUaLWdv6LPP//5Zt0aPQ=="
239 | },
240 | "deepmerge": {
241 | "version": "4.2.2",
242 | "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
243 | "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
244 | "dev": true
245 | },
246 | "escape-string-regexp": {
247 | "version": "1.0.5",
248 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
249 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
250 | "dev": true
251 | },
252 | "estree-walker": {
253 | "version": "2.0.1",
254 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.1.tgz",
255 | "integrity": "sha512-tF0hv+Yi2Ot1cwj9eYHtxC0jB9bmjacjQs6ZBTj82H8JwUywFuc+7E83NWfNMwHXZc11mjfFcVXPe9gEP4B8dg==",
256 | "dev": true
257 | },
258 | "fill-range": {
259 | "version": "7.0.1",
260 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
261 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
262 | "dev": true,
263 | "requires": {
264 | "to-regex-range": "^5.0.1"
265 | }
266 | },
267 | "fs.realpath": {
268 | "version": "1.0.0",
269 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
270 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
271 | "dev": true
272 | },
273 | "fsevents": {
274 | "version": "2.1.3",
275 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz",
276 | "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==",
277 | "dev": true,
278 | "optional": true
279 | },
280 | "get-port": {
281 | "version": "3.2.0",
282 | "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz",
283 | "integrity": "sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw="
284 | },
285 | "glob": {
286 | "version": "7.1.6",
287 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
288 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
289 | "dev": true,
290 | "requires": {
291 | "fs.realpath": "^1.0.0",
292 | "inflight": "^1.0.4",
293 | "inherits": "2",
294 | "minimatch": "^3.0.4",
295 | "once": "^1.3.0",
296 | "path-is-absolute": "^1.0.0"
297 | }
298 | },
299 | "glob-parent": {
300 | "version": "5.1.1",
301 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
302 | "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==",
303 | "dev": true,
304 | "requires": {
305 | "is-glob": "^4.0.1"
306 | }
307 | },
308 | "has-flag": {
309 | "version": "3.0.0",
310 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
311 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
312 | "dev": true
313 | },
314 | "inflight": {
315 | "version": "1.0.6",
316 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
317 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
318 | "dev": true,
319 | "requires": {
320 | "once": "^1.3.0",
321 | "wrappy": "1"
322 | }
323 | },
324 | "inherits": {
325 | "version": "2.0.4",
326 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
327 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
328 | "dev": true
329 | },
330 | "is-binary-path": {
331 | "version": "2.1.0",
332 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
333 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
334 | "dev": true,
335 | "requires": {
336 | "binary-extensions": "^2.0.0"
337 | }
338 | },
339 | "is-extglob": {
340 | "version": "2.1.1",
341 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
342 | "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
343 | "dev": true
344 | },
345 | "is-glob": {
346 | "version": "4.0.1",
347 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
348 | "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
349 | "dev": true,
350 | "requires": {
351 | "is-extglob": "^2.1.1"
352 | }
353 | },
354 | "is-module": {
355 | "version": "1.0.0",
356 | "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
357 | "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=",
358 | "dev": true
359 | },
360 | "is-number": {
361 | "version": "7.0.0",
362 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
363 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
364 | "dev": true
365 | },
366 | "is-reference": {
367 | "version": "1.2.1",
368 | "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
369 | "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==",
370 | "dev": true,
371 | "requires": {
372 | "@types/estree": "*"
373 | }
374 | },
375 | "jest-worker": {
376 | "version": "26.3.0",
377 | "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.3.0.tgz",
378 | "integrity": "sha512-Vmpn2F6IASefL+DVBhPzI2J9/GJUsqzomdeN+P+dK8/jKxbh8R3BtFnx3FIta7wYlPU62cpJMJQo4kuOowcMnw==",
379 | "dev": true,
380 | "requires": {
381 | "@types/node": "*",
382 | "merge-stream": "^2.0.0",
383 | "supports-color": "^7.0.0"
384 | },
385 | "dependencies": {
386 | "has-flag": {
387 | "version": "4.0.0",
388 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
389 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
390 | "dev": true
391 | },
392 | "supports-color": {
393 | "version": "7.1.0",
394 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
395 | "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==",
396 | "dev": true,
397 | "requires": {
398 | "has-flag": "^4.0.0"
399 | }
400 | }
401 | }
402 | },
403 | "js-tokens": {
404 | "version": "4.0.0",
405 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
406 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
407 | "dev": true
408 | },
409 | "kleur": {
410 | "version": "3.0.3",
411 | "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
412 | "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="
413 | },
414 | "livereload": {
415 | "version": "0.9.1",
416 | "resolved": "https://registry.npmjs.org/livereload/-/livereload-0.9.1.tgz",
417 | "integrity": "sha512-9g7sua11kkyZNo2hLRCG3LuZZwqexoyEyecSlV8cAsfAVVCZqLzVir6XDqmH0r+Vzgnd5LrdHDMyjtFnJQLAYw==",
418 | "dev": true,
419 | "requires": {
420 | "chokidar": "^3.3.0",
421 | "livereload-js": "^3.1.0",
422 | "opts": ">= 1.2.0",
423 | "ws": "^6.2.1"
424 | }
425 | },
426 | "livereload-js": {
427 | "version": "3.2.2",
428 | "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-3.2.2.tgz",
429 | "integrity": "sha512-xhScbNeC687ZINjEf/bD+BMiPx4s4q0mehcLb3zCc8+mykOtmaBR4vqzyIV9rIGdG9JjHaT0LiFdscvivCjX1Q==",
430 | "dev": true
431 | },
432 | "local-access": {
433 | "version": "1.0.1",
434 | "resolved": "https://registry.npmjs.org/local-access/-/local-access-1.0.1.tgz",
435 | "integrity": "sha512-ykt2pgN0aqIy6KQC1CqdWTWkmUwNgaOS6dcpHVjyBJONA+Xi7AtSB1vuxC/U/0tjIP3wcRudwQk1YYzUvzk2bA=="
436 | },
437 | "magic-string": {
438 | "version": "0.25.7",
439 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
440 | "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==",
441 | "dev": true,
442 | "requires": {
443 | "sourcemap-codec": "^1.4.4"
444 | }
445 | },
446 | "merge-stream": {
447 | "version": "2.0.0",
448 | "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
449 | "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
450 | "dev": true
451 | },
452 | "mime": {
453 | "version": "2.4.6",
454 | "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz",
455 | "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA=="
456 | },
457 | "minimatch": {
458 | "version": "3.0.4",
459 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
460 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
461 | "dev": true,
462 | "requires": {
463 | "brace-expansion": "^1.1.7"
464 | }
465 | },
466 | "mri": {
467 | "version": "1.1.6",
468 | "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.6.tgz",
469 | "integrity": "sha512-oi1b3MfbyGa7FJMP9GmLTttni5JoICpYBRlq+x5V16fZbLsnL9N3wFqqIm/nIG43FjUFkFh9Epzp/kzUGUnJxQ=="
470 | },
471 | "normalize-path": {
472 | "version": "3.0.0",
473 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
474 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
475 | "dev": true
476 | },
477 | "once": {
478 | "version": "1.4.0",
479 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
480 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
481 | "dev": true,
482 | "requires": {
483 | "wrappy": "1"
484 | }
485 | },
486 | "opts": {
487 | "version": "1.2.7",
488 | "resolved": "https://registry.npmjs.org/opts/-/opts-1.2.7.tgz",
489 | "integrity": "sha512-hwZhzGGG/GQ7igxAVFOEun2N4fWul31qE9nfBdCnZGQCB5+L7tN9xZ+94B4aUpLOJx/of3zZs5XsuubayQYQjA==",
490 | "dev": true
491 | },
492 | "path-is-absolute": {
493 | "version": "1.0.1",
494 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
495 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
496 | "dev": true
497 | },
498 | "path-parse": {
499 | "version": "1.0.6",
500 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
501 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
502 | "dev": true
503 | },
504 | "picomatch": {
505 | "version": "2.2.2",
506 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
507 | "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==",
508 | "dev": true
509 | },
510 | "randombytes": {
511 | "version": "2.1.0",
512 | "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
513 | "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
514 | "dev": true,
515 | "requires": {
516 | "safe-buffer": "^5.1.0"
517 | }
518 | },
519 | "readdirp": {
520 | "version": "3.4.0",
521 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz",
522 | "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==",
523 | "dev": true,
524 | "requires": {
525 | "picomatch": "^2.2.1"
526 | }
527 | },
528 | "require-relative": {
529 | "version": "0.8.7",
530 | "resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz",
531 | "integrity": "sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=",
532 | "dev": true
533 | },
534 | "resolve": {
535 | "version": "1.17.0",
536 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz",
537 | "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==",
538 | "dev": true,
539 | "requires": {
540 | "path-parse": "^1.0.6"
541 | }
542 | },
543 | "rollup": {
544 | "version": "2.26.6",
545 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.26.6.tgz",
546 | "integrity": "sha512-iSB7eE3k/VNQHnI7ckS++4yIqTamoUCB1xo7MswhJ/fg22oFYR5+xCrUZVviBj97jvc5A31MPbVMw1Wc3jWxmw==",
547 | "dev": true,
548 | "requires": {
549 | "fsevents": "~2.1.2"
550 | }
551 | },
552 | "rollup-plugin-livereload": {
553 | "version": "1.3.0",
554 | "resolved": "https://registry.npmjs.org/rollup-plugin-livereload/-/rollup-plugin-livereload-1.3.0.tgz",
555 | "integrity": "sha512-abyqXaB21+nFHo+vJULBqfzNx6zXABC19UyvqgDfdoxR/8pFAd041GO+GIUe8ZYC2DbuMUmioh1Lvbk14YLZgw==",
556 | "dev": true,
557 | "requires": {
558 | "livereload": "^0.9.1"
559 | }
560 | },
561 | "rollup-plugin-svelte": {
562 | "version": "6.0.0",
563 | "resolved": "https://registry.npmjs.org/rollup-plugin-svelte/-/rollup-plugin-svelte-6.0.0.tgz",
564 | "integrity": "sha512-y9qtWa+iNYwXdOZqaEqz3i6k3gzofC9JXzv+WVKDOt0DLiJxJaSrlKKf4YkKG91RzTK5Lo+0fW8in9QH/DxEhA==",
565 | "dev": true,
566 | "requires": {
567 | "require-relative": "^0.8.7",
568 | "rollup-pluginutils": "^2.8.2",
569 | "sourcemap-codec": "^1.4.8"
570 | }
571 | },
572 | "rollup-plugin-terser": {
573 | "version": "7.0.0",
574 | "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.0.tgz",
575 | "integrity": "sha512-p/N3lLiFusCjYTLfVkoaiRTOGr5AESEaljMPH12MhOtoMkmTBhIAfuadrcWy4am1U0vU4WTxO9fi0K09O4CboQ==",
576 | "dev": true,
577 | "requires": {
578 | "@babel/code-frame": "^7.10.4",
579 | "jest-worker": "^26.2.1",
580 | "serialize-javascript": "^4.0.0",
581 | "terser": "^5.0.0"
582 | }
583 | },
584 | "rollup-pluginutils": {
585 | "version": "2.8.2",
586 | "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz",
587 | "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==",
588 | "dev": true,
589 | "requires": {
590 | "estree-walker": "^0.6.1"
591 | },
592 | "dependencies": {
593 | "estree-walker": {
594 | "version": "0.6.1",
595 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz",
596 | "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==",
597 | "dev": true
598 | }
599 | }
600 | },
601 | "sade": {
602 | "version": "1.7.3",
603 | "resolved": "https://registry.npmjs.org/sade/-/sade-1.7.3.tgz",
604 | "integrity": "sha512-m4BctppMvJ60W1dXnHq7jMmFe3hPJZDAH85kQ3ACTo7XZNVUuTItCQ+2HfyaMeV5cKrbw7l4vD/6We3GBxvdJw==",
605 | "requires": {
606 | "mri": "^1.1.0"
607 | }
608 | },
609 | "safe-buffer": {
610 | "version": "5.2.1",
611 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
612 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
613 | "dev": true
614 | },
615 | "semiver": {
616 | "version": "1.1.0",
617 | "resolved": "https://registry.npmjs.org/semiver/-/semiver-1.1.0.tgz",
618 | "integrity": "sha512-QNI2ChmuioGC1/xjyYwyZYADILWyW6AmS1UH6gDj/SFUUUS4MBAWs/7mxnkRPc/F4iHezDP+O8t0dO8WHiEOdg=="
619 | },
620 | "serialize-javascript": {
621 | "version": "4.0.0",
622 | "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz",
623 | "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==",
624 | "dev": true,
625 | "requires": {
626 | "randombytes": "^2.1.0"
627 | }
628 | },
629 | "sirv": {
630 | "version": "1.0.6",
631 | "resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.6.tgz",
632 | "integrity": "sha512-LRGu7Op4Xl9hhigOy2kcB53zAYTjNDdpooey49dIU0cMdpOv9ithVf7nstk3jvs8EhMiT/VORoyazZYGgw4vnA==",
633 | "requires": {
634 | "@polka/url": "^1.0.0-next.9",
635 | "mime": "^2.3.1",
636 | "totalist": "^1.0.0"
637 | }
638 | },
639 | "sirv-cli": {
640 | "version": "1.0.6",
641 | "resolved": "https://registry.npmjs.org/sirv-cli/-/sirv-cli-1.0.6.tgz",
642 | "integrity": "sha512-K/iY1OHG7hTw4GzLoqMhwzKCbgWmx5joYAAF2+CwyiamWCpVzAgNVWgAc0JmSA2Gf3wseov05il2QbFTGTZMVg==",
643 | "requires": {
644 | "console-clear": "^1.1.0",
645 | "get-port": "^3.2.0",
646 | "kleur": "^3.0.0",
647 | "local-access": "^1.0.1",
648 | "sade": "^1.6.0",
649 | "semiver": "^1.0.0",
650 | "sirv": "^1.0.6",
651 | "tinydate": "^1.0.0"
652 | }
653 | },
654 | "source-map": {
655 | "version": "0.6.1",
656 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
657 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
658 | "dev": true
659 | },
660 | "source-map-support": {
661 | "version": "0.5.19",
662 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
663 | "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==",
664 | "dev": true,
665 | "requires": {
666 | "buffer-from": "^1.0.0",
667 | "source-map": "^0.6.0"
668 | }
669 | },
670 | "sourcemap-codec": {
671 | "version": "1.4.8",
672 | "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
673 | "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
674 | "dev": true
675 | },
676 | "supports-color": {
677 | "version": "5.5.0",
678 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
679 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
680 | "dev": true,
681 | "requires": {
682 | "has-flag": "^3.0.0"
683 | }
684 | },
685 | "svelte": {
686 | "version": "3.24.1",
687 | "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.24.1.tgz",
688 | "integrity": "sha512-OX/IBVUJSFo1rnznXdwf9rv6LReJ3qQ0PwRjj76vfUWyTfbHbR9OXqJBnUrpjyis2dwYcbT2Zm1DFjOOF1ZbbQ==",
689 | "dev": true
690 | },
691 | "terser": {
692 | "version": "5.2.1",
693 | "resolved": "https://registry.npmjs.org/terser/-/terser-5.2.1.tgz",
694 | "integrity": "sha512-/AOtjRtAMNGO0fIF6m8HfcvXTw/2AKpsOzDn36tA5RfhRdeXyb4RvHxJ5Pah7iL6dFkLk+gOnCaNHGwJPl6TrQ==",
695 | "dev": true,
696 | "requires": {
697 | "commander": "^2.20.0",
698 | "source-map": "~0.6.1",
699 | "source-map-support": "~0.5.12"
700 | }
701 | },
702 | "tinydate": {
703 | "version": "1.3.0",
704 | "resolved": "https://registry.npmjs.org/tinydate/-/tinydate-1.3.0.tgz",
705 | "integrity": "sha512-7cR8rLy2QhYHpsBDBVYnnWXm8uRTr38RoZakFSW7Bs7PzfMPNZthuMLkwqZv7MTu8lhQ91cOFYS5a7iFj2oR3w=="
706 | },
707 | "to-regex-range": {
708 | "version": "5.0.1",
709 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
710 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
711 | "dev": true,
712 | "requires": {
713 | "is-number": "^7.0.0"
714 | }
715 | },
716 | "totalist": {
717 | "version": "1.1.0",
718 | "resolved": "https://registry.npmjs.org/totalist/-/totalist-1.1.0.tgz",
719 | "integrity": "sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g=="
720 | },
721 | "wrappy": {
722 | "version": "1.0.2",
723 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
724 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
725 | "dev": true
726 | },
727 | "ws": {
728 | "version": "6.2.1",
729 | "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz",
730 | "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==",
731 | "dev": true,
732 | "requires": {
733 | "async-limiter": "~1.0.0"
734 | }
735 | }
736 | }
737 | }
738 |
--------------------------------------------------------------------------------
/svelte-image-search/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "svelte-app",
3 | "version": "1.0.0",
4 | "scripts": {
5 | "build": "rollup -c",
6 | "dev": "rollup -c -w",
7 | "start": "sirv public"
8 | },
9 | "devDependencies": {
10 | "@rollup/plugin-commonjs": "15.0.0",
11 | "@rollup/plugin-node-resolve": "^9.0.0",
12 | "rollup": "^2.26.6",
13 | "rollup-plugin-livereload": "^1.3.0",
14 | "rollup-plugin-svelte": "^6.0.0",
15 | "rollup-plugin-terser": "^7.0.0",
16 | "svelte": "^3.24.1"
17 | },
18 | "dependencies": {
19 | "sirv-cli": "^1.0.6"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/svelte-image-search/public/.gitignore:
--------------------------------------------------------------------------------
1 | .now
--------------------------------------------------------------------------------
/svelte-image-search/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodingGarden/frontend-framework-showdown-2020/bc113954b3114e0b2c6ae73b4761b816908e0e95/svelte-image-search/public/favicon.png
--------------------------------------------------------------------------------
/svelte-image-search/public/global.css:
--------------------------------------------------------------------------------
1 | body {
2 | width: 80%;
3 | margin: 2em auto 0 auto;
4 | }
5 |
6 | .images {
7 | column-count: 3;
8 | }
9 |
10 | img {
11 | width: 100%;
12 | }
13 |
14 | @media (max-width: 1200px) {
15 | .images {
16 | column-count: 2;
17 | }
18 | }
19 |
20 | @media (max-width: 800px) {
21 | .images {
22 | column-count: 1;
23 | }
24 | }
--------------------------------------------------------------------------------
/svelte-image-search/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Svelte app
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/svelte-image-search/public/now.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 2,
3 | "alias": [
4 | "svelte-image-search"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/svelte-image-search/rollup.config.js:
--------------------------------------------------------------------------------
1 | import svelte from 'rollup-plugin-svelte';
2 | import resolve from '@rollup/plugin-node-resolve';
3 | import commonjs from '@rollup/plugin-commonjs';
4 | import livereload from 'rollup-plugin-livereload';
5 | import { terser } from 'rollup-plugin-terser';
6 |
7 | const production = !process.env.ROLLUP_WATCH;
8 |
9 | export default {
10 | input: 'src/main.js',
11 | output: {
12 | sourcemap: true,
13 | format: 'iife',
14 | name: 'app',
15 | file: 'public/build/bundle.js'
16 | },
17 | plugins: [
18 | svelte({
19 | // enable run-time checks when not in production
20 | dev: !production,
21 | // we'll extract any component CSS out into
22 | // a separate file - better for performance
23 | css: css => {
24 | css.write('public/build/bundle.css');
25 | }
26 | }),
27 |
28 | // If you have external dependencies installed from
29 | // npm, you'll most likely need these plugins. In
30 | // some cases you'll need additional configuration -
31 | // consult the documentation for details:
32 | // https://github.com/rollup/plugins/tree/master/packages/commonjs
33 | resolve({
34 | browser: true,
35 | dedupe: ['svelte']
36 | }),
37 | commonjs(),
38 |
39 | // In dev mode, call `npm run start` once
40 | // the bundle has been generated
41 | !production && serve(),
42 |
43 | // Watch the `public` directory and refresh the
44 | // browser on changes when not in production
45 | !production && livereload('public'),
46 |
47 | // If we're building for production (npm run build
48 | // instead of npm run dev), minify
49 | production && terser()
50 | ],
51 | watch: {
52 | clearScreen: false
53 | }
54 | };
55 |
56 | function serve() {
57 | let started = false;
58 |
59 | return {
60 | writeBundle() {
61 | if (!started) {
62 | started = true;
63 |
64 | require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], {
65 | stdio: ['ignore', 'inherit', 'inherit'],
66 | shell: true
67 | });
68 | }
69 | }
70 | };
71 | }
72 |
--------------------------------------------------------------------------------
/svelte-image-search/src/App.svelte:
--------------------------------------------------------------------------------
1 |
15 |
16 | Svelte Image Search
17 |
22 | {#if loading}
23 |
24 | {/if}
25 |
26 | {#each images as image}
27 |
28 | {/each}
29 |
--------------------------------------------------------------------------------
/svelte-image-search/src/api.js:
--------------------------------------------------------------------------------
1 | const API_URL = 'https://nature-image-api.now.sh/search?q=';
2 |
3 | export default async function getImages(searchTerm) {
4 | const response = await fetch(`${API_URL}${searchTerm}`);
5 | const json = await response.json();
6 | return json.images.map(({ image }) => image);
7 | }
8 |
--------------------------------------------------------------------------------
/svelte-image-search/src/main.js:
--------------------------------------------------------------------------------
1 | import App from './App.svelte';
2 |
3 | const app = new App({
4 | target: document.body,
5 | });
6 |
7 | export default app;
--------------------------------------------------------------------------------
/vanilla-image-search/.gitignore:
--------------------------------------------------------------------------------
1 | .now
--------------------------------------------------------------------------------
/vanilla-image-search/README.md:
--------------------------------------------------------------------------------
1 | * [x] Setup
2 | * [x] User Input, DOM Events
3 | * [x] Retrieve the search term from the input when the form is submitted
4 | * [x] Conditional Rendering
5 | * [x] Show loading image when form is submitted
6 | * [x] API Requests
7 | * [x] Request the images from the API with the given search term
8 | * [x] Rendering Lists of Data
9 | * [x] Append the API results to the page
10 | * [x] Conditional Rendering
11 | * [x] Hide loading image
--------------------------------------------------------------------------------
/vanilla-image-search/api.js:
--------------------------------------------------------------------------------
1 | const API_URL = `https://nature-image-api.now.sh/search?q=`;
2 |
3 | export default async function getImages(searchTerm) {
4 | const response = await fetch(`${API_URL}${searchTerm}`);
5 | const json = await response.json();
6 | return json.images;
7 | }
8 |
9 | // export default function getImages(searchTerm) {
10 | // return fetch(`${API_URL}${searchTerm}`)
11 | // .then(response => response.json())
12 | // .then(json => {
13 | // return json.images;
14 | // });
15 | // }
--------------------------------------------------------------------------------
/vanilla-image-search/app.js:
--------------------------------------------------------------------------------
1 | import getImages from './api.js';
2 |
3 | const form = document.querySelector('form');
4 | const loadingImage = document.querySelector('#loadingImage');
5 | const imagesSection = document.querySelector('.images');
6 |
7 | loadingImage.style.display = 'none';
8 |
9 | form.addEventListener('submit', onFormSubmitted);
10 | // form.onsubmit = onFormSubmitted;
11 |
12 | // function onFormSubmitted(event) {
13 | // event.preventDefault();
14 | // imagesSection.innerHTML = '';
15 | // const formData = new FormData(form);
16 | // const searchTerm = formData.get('searchTerm');
17 | // loadingImage.style.display = '';
18 | // getImages(searchTerm).then(addImagesToPage);
19 | // }
20 |
21 | async function onFormSubmitted(event) {
22 | event.preventDefault();
23 | imagesSection.innerHTML = '';
24 | const formData = new FormData(form);
25 | const searchTerm = formData.get('searchTerm');
26 | loadingImage.style.display = '';
27 | try {
28 | const images = await getImages(searchTerm);
29 | addImagesToPage(images);
30 | } catch (error) {
31 | // show an error on the page...
32 | }
33 | }
34 |
35 | function addImagesToPage(images) {
36 | // WARNING: VULNERABLE TO XSS IF NOT SANITIZE
37 | // imagesSection.innerHTML = images.map((image) => {
38 | // return `
`;
39 | // }).join('');
40 | // imagesSection.innerHTML = images.reduce((html, image) => {
41 | // return html + `
`;
42 | // }, '');
43 | images.forEach((item) => {
44 | const imageElement = document.createElement('img');
45 | imageElement.src = item.image;
46 | imagesSection.append(imageElement);
47 | });
48 | loadingImage.style.display = 'none';
49 | }
--------------------------------------------------------------------------------
/vanilla-image-search/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Vanilla Image Search
7 |
8 |
9 |
10 |
11 | Vanilla Image Search
12 |
17 |
18 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/vanilla-image-search/now.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 2,
3 | "builds": [
4 | {
5 | "src": "*",
6 | "use": "@now/static"
7 | }
8 | ],
9 | "alias": [
10 | "vanilla-image-search"
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/vanilla-image-search/styles.css:
--------------------------------------------------------------------------------
1 | body {
2 | width: 80%;
3 | margin: 2em auto 0 auto;
4 | }
5 |
6 | .images {
7 | column-count: 3;
8 | }
9 |
10 | img {
11 | width: 100%;
12 | }
13 |
14 | @media (max-width: 1200px) {
15 | .images {
16 | column-count: 2;
17 | }
18 | }
19 |
20 | @media (max-width: 800px) {
21 | .images {
22 | column-count: 1;
23 | }
24 | }
--------------------------------------------------------------------------------
/vue-image-search/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 | not dead
4 |
--------------------------------------------------------------------------------
/vue-image-search/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.{js,jsx,ts,tsx,vue}]
2 | indent_style = space
3 | indent_size = 2
4 | end_of_line = lf
5 | trim_trailing_whitespace = true
6 | insert_final_newline = true
7 | max_line_length = 100
8 |
--------------------------------------------------------------------------------
/vue-image-search/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true,
5 | },
6 | extends: [
7 | 'plugin:vue/essential',
8 | '@vue/airbnb',
9 | ],
10 | parserOptions: {
11 | parser: 'babel-eslint',
12 | },
13 | rules: {
14 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
15 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
16 | },
17 | };
18 |
--------------------------------------------------------------------------------
/vue-image-search/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # local env files
6 | .env.local
7 | .env.*.local
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | # Editor directories and files
15 | .idea
16 | .vscode
17 | *.suo
18 | *.ntvs*
19 | *.njsproj
20 | *.sln
21 | *.sw?
22 |
--------------------------------------------------------------------------------
/vue-image-search/README.md:
--------------------------------------------------------------------------------
1 | * [x] Setup
2 | * [x] User Input, DOM Events
3 | * [x] Retrieve the search term from the input when the form is submitted
4 | * [x] Conditional Rendering
5 | * [x] Show loading image when form is submitted
6 | * [x] API Requests
7 | * [x] Request the images from the API with the given search term
8 | * [x] Rendering Lists of Data
9 | * [x] Append the API results to the page
10 | * [x] Conditional Rendering
11 | * [x] Hide loading image
--------------------------------------------------------------------------------
/vue-image-search/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset',
4 | ],
5 | };
6 |
--------------------------------------------------------------------------------
/vue-image-search/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-image-search",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "lint": "vue-cli-service lint"
9 | },
10 | "dependencies": {
11 | "core-js": "^3.6.5",
12 | "vue": "^2.6.12"
13 | },
14 | "devDependencies": {
15 | "@vue/cli-plugin-babel": "~4.5.4",
16 | "@vue/cli-plugin-eslint": "~4.5.4",
17 | "@vue/cli-service": "~4.5.4",
18 | "@vue/eslint-config-airbnb": "^5.1.0",
19 | "babel-eslint": "^10.1.0",
20 | "eslint": "^7.7.0",
21 | "eslint-plugin-import": "^2.22.0",
22 | "eslint-plugin-vue": "^6.2.2",
23 | "vue-template-compiler": "^2.6.12"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/vue-image-search/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodingGarden/frontend-framework-showdown-2020/bc113954b3114e0b2c6ae73b4761b816908e0e95/vue-image-search/public/favicon.ico
--------------------------------------------------------------------------------
/vue-image-search/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
10 |
11 |
12 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/vue-image-search/public/now.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 2,
3 | "builds": [
4 | {
5 | "src": "*",
6 | "use": "@now/static"
7 | }
8 | ],
9 | "alias": [
10 | "vue-image-search"
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/vue-image-search/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Vue Image Search
4 |
9 |

10 |
11 |
12 |
13 |
14 |
15 |
16 |
36 |
37 |
63 |
--------------------------------------------------------------------------------
/vue-image-search/src/api.js:
--------------------------------------------------------------------------------
1 | const API_URL = 'https://nature-image-api.now.sh/search?q=';
2 |
3 | export default async function getImages(searchTerm) {
4 | const response = await fetch(`${API_URL}${searchTerm}`);
5 | const json = await response.json();
6 | return json.images.map(({ image }) => image);
7 | }
8 |
--------------------------------------------------------------------------------
/vue-image-search/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodingGarden/frontend-framework-showdown-2020/bc113954b3114e0b2c6ae73b4761b816908e0e95/vue-image-search/src/assets/logo.png
--------------------------------------------------------------------------------
/vue-image-search/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import App from './App.vue';
3 |
4 | Vue.config.productionTip = false;
5 |
6 | new Vue({
7 | render: (h) => h(App),
8 | }).$mount('#app');
9 |
--------------------------------------------------------------------------------
/vue-image-search/vue.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | lintOnSave: false,
3 | };
4 |
--------------------------------------------------------------------------------