├── .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 |
44 | 45 | 46 | 47 |
48 | 49 |
50 | 51 |
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 |
3 | 4 | 5 | 6 |
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 |
28 | 29 | 37 | 38 |
39 | {loading && ( 40 | loading 45 | )} 46 | {/* {loading ? ( 47 | loading 52 | ) : null} */} 53 |
54 | {images.map(url => ( 55 | {searchTerm} 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 | 2 | 3 | 4 | 5 | 6 | 7 | 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 |
18 | 19 | 20 | 21 |
22 | {#if loading} 23 | loading 24 | {/if} 25 |
26 | {#each images as image} 27 | {searchTerm} 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 |
13 | 14 | 15 | 16 |
17 | 18 |
19 | 20 |
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 | 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 | --------------------------------------------------------------------------------