├── .eslintrc ├── .gitignore ├── .npmrc ├── .vscode ├── extensions.json └── settings.json ├── exercises ├── .gitkeep ├── compound-start │ ├── compound.ts │ ├── index.html │ ├── package-lock.json │ ├── package.json │ └── style.css ├── compound │ ├── compound.ts │ ├── index.html │ ├── package-lock.json │ ├── package.json │ └── style.css ├── dad-jokes-start │ ├── index.html │ ├── jokes.ts │ └── style.css ├── dad-jokes │ ├── index.html │ ├── jokes.ts │ └── style.css ├── focus-machine-start │ ├── css │ │ └── style.css │ ├── icons │ │ ├── Airplane.svg │ │ ├── Airport.svg │ │ ├── Bus.svg │ │ ├── Car.svg │ │ ├── Construction.svg │ │ ├── Crowd.svg │ │ ├── Fire.svg │ │ ├── Football.svg │ │ ├── Highway.svg │ │ ├── Laundry.svg │ │ ├── Lobby.svg │ │ ├── Noise.svg │ │ ├── Park.svg │ │ ├── Restaurant.svg │ │ ├── School.svg │ │ ├── Talking.svg │ │ ├── Traffic.svg │ │ ├── burger.svg │ │ ├── city.svg │ │ ├── coaster.svg │ │ ├── coffee.svg │ │ ├── doors.svg │ │ ├── files.txt │ │ ├── fountain.svg │ │ ├── grocery.svg │ │ ├── hockey.svg │ │ ├── mall.svg │ │ ├── market.svg │ │ ├── party.svg │ │ ├── rain.svg │ │ ├── river.svg │ │ ├── speaker.svg │ │ ├── subway.svg │ │ ├── waterfall.svg │ │ ├── waves.svg │ │ ├── wind.sv │ │ └── wind.svg │ ├── index.html │ ├── sounds.ts │ └── utils │ │ └── .gitkeep ├── focus-machine │ ├── css │ │ └── style.css │ ├── delegate.ts │ ├── icons │ │ ├── Airplane.svg │ │ ├── Airport.svg │ │ ├── Bus.svg │ │ ├── Car.svg │ │ ├── Construction.svg │ │ ├── Crowd.svg │ │ ├── Fire.svg │ │ ├── Football.svg │ │ ├── Highway.svg │ │ ├── Laundry.svg │ │ ├── Lobby.svg │ │ ├── Noise.svg │ │ ├── Park.svg │ │ ├── Restaurant.svg │ │ ├── School.svg │ │ ├── Talking.svg │ │ ├── Traffic.svg │ │ ├── burger.svg │ │ ├── city.svg │ │ ├── coaster.svg │ │ ├── coffee.svg │ │ ├── doors.svg │ │ ├── files.txt │ │ ├── fountain.svg │ │ ├── grocery.svg │ │ ├── hockey.svg │ │ ├── mall.svg │ │ ├── market.svg │ │ ├── party.svg │ │ ├── rain.svg │ │ ├── river.svg │ │ ├── speaker.svg │ │ ├── subway.svg │ │ ├── waterfall.svg │ │ ├── waves.svg │ │ ├── wind.sv │ │ └── wind.svg │ ├── index.html │ ├── index.ts │ ├── sounds.ts │ └── utils │ │ ├── .gitkeep │ │ └── dom.ts ├── hotdog-start │ ├── hotdog.ts │ ├── images │ │ └── doggy.svg │ ├── index.html │ ├── package.json │ └── style.css ├── hotdog │ ├── celebrate.ts │ ├── hotdog.ts │ ├── images │ │ └── doggy.svg │ ├── index.html │ ├── package.json │ └── style.css └── type-cardio │ ├── type-cardio-START.ts │ └── type-cardio-finishedx.ts ├── finished-exercises ├── CRUD │ ├── app.ts │ ├── fun.ts │ ├── index.html │ ├── package-lock.json │ ├── package.json │ └── style.css ├── augmented-reality │ ├── ar.ts │ ├── database.ts │ ├── fonts │ │ └── material-symbols.woff │ ├── index.html │ ├── package-lock.json │ ├── package.json │ ├── qrcodes │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── apple.png │ │ └── banana.png │ ├── style.css │ ├── utils │ │ └── getImageData.ts │ └── vite.config.ts ├── complain-together │ ├── another.css │ ├── complain.html │ ├── complain.ts │ ├── lib │ │ ├── convert.ts │ │ └── rates.ts │ ├── style.css │ ├── tsconfig.json │ └── types.ts ├── compound │ ├── compound.ts │ ├── index.html │ ├── package-lock.json │ ├── package.json │ └── style.css ├── dad-jokes │ ├── index.html │ ├── jokes.ts │ └── style.css ├── dragon-drop │ ├── dragon.ts │ ├── index.html │ ├── style.css │ └── tsconfig.json ├── focus-machine │ ├── css │ │ └── style.css │ ├── dom.ts │ ├── icons │ │ ├── Airplane.svg │ │ ├── Airport.svg │ │ ├── Bus.svg │ │ ├── Car.svg │ │ ├── Construction.svg │ │ ├── Crowd.svg │ │ ├── Fire.svg │ │ ├── Football.svg │ │ ├── Highway.svg │ │ ├── Laundry.svg │ │ ├── Lobby.svg │ │ ├── Noise.svg │ │ ├── Park.svg │ │ ├── Restaurant.svg │ │ ├── School.svg │ │ ├── Talking.svg │ │ ├── Traffic.svg │ │ ├── burger.svg │ │ ├── city.svg │ │ ├── coaster.svg │ │ ├── coffee.svg │ │ ├── doors.svg │ │ ├── files.txt │ │ ├── fountain.svg │ │ ├── grocery.svg │ │ ├── hockey.svg │ │ ├── mall.svg │ │ ├── market.svg │ │ ├── party.svg │ │ ├── rain.svg │ │ ├── river.svg │ │ ├── speaker.svg │ │ ├── subway.svg │ │ ├── waterfall.svg │ │ ├── waves.svg │ │ ├── wind.sv │ │ └── wind.svg │ ├── index.html │ ├── index.ts │ ├── sounds.ts │ └── utils │ │ └── delegate.ts ├── hotdog │ ├── fireworks.ts │ ├── hotdog-coco.ts │ ├── hotdog.ts │ ├── images │ │ └── doggy.svg │ ├── index.html │ ├── ml5.d.ts │ ├── package.json │ └── style.css ├── index.html ├── money │ ├── currencies.ts │ ├── index.html │ ├── money.css │ └── money.ts ├── repetiton-timer │ ├── index.html │ ├── package-lock.json │ ├── package.json │ ├── speech.ts │ ├── style.css │ └── timer.ts ├── voice-changer │ ├── audio │ │ └── wes-talking.m4a │ ├── effects │ │ └── anon.ts │ ├── index.html │ └── voice.ts └── weight-conversion │ ├── index.html │ ├── src │ ├── index.ts │ └── weights.ts │ └── style.css ├── index.html ├── package-lock.json ├── package.json ├── playground-finished ├── DOM.ts ├── any-vs-unknown.ts ├── assertions.ts ├── basics.ts ├── classes-interface.ts ├── classes-keywords.ts ├── classes.ts ├── const-assertions.ts ├── custom-types.ts ├── date.html ├── date.ts ├── discriminating-union.ts ├── enums.html ├── enums.ts ├── error-handling-promises.ts ├── extending-and-merging.ts ├── filtering.ts ├── function-overloading.ts ├── generics.ts ├── im-vs-ex.ts ├── import-export │ ├── existing.d.ts │ ├── existing.js │ ├── typing.ts │ └── utils.ts ├── jsdoc.ts ├── looping.ts ├── mapped.ts ├── nothing.ts ├── promises.ts ├── reduce.ts ├── satisfies.ts ├── template-literal-types.ts ├── template-string-types.ts ├── tuple-response.ts ├── tuples.html ├── tuples.ts ├── type-guards.ts ├── types-from-data.ts ├── typing-functions-START.ts ├── unions.ts ├── utility-types-START.ts └── utility-types.ts ├── playground-finished?? ├── reduce.ts ├── satisfies.ts ├── teest.js ├── test.ts └── typing-functions-FINISHED.ts ├── playground └── satisfies.ts ├── readme.md ├── test-data └── bands.ts ├── tsconfig.json └── vite.config.ts /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "wesbos/typescript" 4 | ], 5 | "rules": { 6 | "@typescript-eslint/no-inferrable-types": "off", 7 | "@typescript-eslint/no-unused-vars": "off", 8 | "@typescript-eslint/ban-types": "off" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | haters/ 4 | .eslintcache 5 | .idea 6 | *.local 7 | *.log 8 | dist 9 | dist-ssr 10 | sounds/ 11 | .parcel-cache/ 12 | hot_dog/ 13 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | fund=false 2 | audit=false 3 | legacy-peer-deps=true 4 | npm_config_loglevel=error 5 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint", 4 | "wesbos.theme-cobalt2", 5 | "usernamehw.errorlens" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // Inlay Hints 3 | "editor.inlayHints.enabled": "offUnlessPressed", // Show on Ctrl + Option (mac) / Ctrl + Alt (windows) 4 | "editor.inlayHints.fontSize": 12, 5 | "editor.inlayHints.padding": true, 6 | "typescript.inlayHints.enumMemberValues.enabled": true, 7 | "typescript.inlayHints.propertyDeclarationTypes.enabled": true, 8 | "typescript.inlayHints.variableTypes.enabled": true, 9 | "typescript.inlayHints.functionLikeReturnTypes.enabled": true, 10 | // We can use this to turn on "problems" tab for all files 11 | "typescript.tsserver.experimental.enableProjectDiagnostics": false, 12 | // Error Lens Settings 13 | "errorLens.fontSize": "17", 14 | // "errorLens.fontFamily": "Comic Sans MS", 15 | "errorLens.onSave": true, 16 | "errorLens.enabledDiagnosticLevels": [ 17 | "error", 18 | "warning", 19 | "hint" 20 | ], 21 | "workbench.colorCustomizations": { 22 | "errorLens.hintForeground": "#ffc600A1", 23 | "errorLens.hintBackground": "#ffc60003", 24 | "errorLens.errorBackground": "#ff000005" 25 | }, 26 | "errorLens.exclude": [ 27 | "never (read|used)", 28 | "Unknown word" 29 | ], 30 | } 31 | -------------------------------------------------------------------------------- /exercises/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/Tasty-TypeScript/9b3c2e8b1480cdc1c80aeac7d9b1789dd0f6b7d1/exercises/.gitkeep -------------------------------------------------------------------------------- /exercises/compound-start/compound.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/Tasty-TypeScript/9b3c2e8b1480cdc1c80aeac7d9b1789dd0f6b7d1/exercises/compound-start/compound.ts -------------------------------------------------------------------------------- /exercises/compound-start/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Compound 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 |
18 | 19 | 20 |

How much will you start with?

21 |
22 |
23 | 24 | 25 |

What % of return do you expect?

26 |
27 |
28 | 29 | 30 |

How many years will you invest?

31 |
32 |
33 | 34 | 35 |

How much will you contribute each period?

36 |
37 |
38 | 39 | 46 |

How often will you contribute?

47 |
48 |
49 |
50 | 51 |
52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /exercises/compound-start/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "compound", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "compound", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "financial": "^0.1.3" 13 | } 14 | }, 15 | "node_modules/financial": { 16 | "version": "0.1.3", 17 | "resolved": "https://registry.npmjs.org/financial/-/financial-0.1.3.tgz", 18 | "integrity": "sha512-vdv2nx8x+u1bgfcdt/BsKU4Gpqfu9u2485UJoQAs6GqrASszm6QWY7d8H7x968G5rAdV4SU2Nz4JoLTtHUnlcA==", 19 | "engines": { 20 | "node": ">=12" 21 | } 22 | } 23 | }, 24 | "dependencies": { 25 | "financial": { 26 | "version": "0.1.3", 27 | "resolved": "https://registry.npmjs.org/financial/-/financial-0.1.3.tgz", 28 | "integrity": "sha512-vdv2nx8x+u1bgfcdt/BsKU4Gpqfu9u2485UJoQAs6GqrASszm6QWY7d8H7x968G5rAdV4SU2Nz4JoLTtHUnlcA==" 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /exercises/compound-start/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "compound-start", 3 | "version": "1.0.0", 4 | "author": "Wes Bos", 5 | "license": "ISC", 6 | "main": "./compound.ts", 7 | "dependencies": { 8 | "financial": "^0.1.3" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /exercises/compound/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "compound", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "compound", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "financial": "^0.1.3" 13 | } 14 | }, 15 | "node_modules/financial": { 16 | "version": "0.1.3", 17 | "resolved": "https://registry.npmjs.org/financial/-/financial-0.1.3.tgz", 18 | "integrity": "sha512-vdv2nx8x+u1bgfcdt/BsKU4Gpqfu9u2485UJoQAs6GqrASszm6QWY7d8H7x968G5rAdV4SU2Nz4JoLTtHUnlcA==", 19 | "engines": { 20 | "node": ">=12" 21 | } 22 | } 23 | }, 24 | "dependencies": { 25 | "financial": { 26 | "version": "0.1.3", 27 | "resolved": "https://registry.npmjs.org/financial/-/financial-0.1.3.tgz", 28 | "integrity": "sha512-vdv2nx8x+u1bgfcdt/BsKU4Gpqfu9u2485UJoQAs6GqrASszm6QWY7d8H7x968G5rAdV4SU2Nz4JoLTtHUnlcA==" 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /exercises/compound/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "compound-working", 3 | "version": "1.0.0", 4 | "author": "Wes Bos", 5 | "license": "ISC", 6 | "main": "./compound.ts", 7 | "dependencies": { 8 | "financial": "^0.1.3" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /exercises/dad-jokes-start/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | Dad Jokes 14 | 15 | 16 | 17 |
18 | 25 |
26 |
27 |

Dad Jokes.

28 |
29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /exercises/dad-jokes-start/jokes.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/Tasty-TypeScript/9b3c2e8b1480cdc1c80aeac7d9b1789dd0f6b7d1/exercises/dad-jokes-start/jokes.ts -------------------------------------------------------------------------------- /exercises/dad-jokes-start/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | --size: 20px; 3 | --pink: #f5afc4; 4 | --black: #181718; 5 | --white: #fff; 6 | font-family: "Varela Round", sans-serif; 7 | background: var(--pink); 8 | color: var(--black); 9 | padding: 50px; 10 | } 11 | 12 | .wrapper { 13 | text-align: center; 14 | container-type: inline-size; 15 | } 16 | 17 | .joke { 18 | font-size: 5cqw; 19 | padding: 20px; 20 | font-weight: 900; 21 | text-align: balance; 22 | text-align: center; 23 | } 24 | 25 | button { 26 | font-family: "Varela Round", sans-serif; 27 | background: var(--black); 28 | color: var(--white); 29 | border: 0; 30 | font-size: 30px; 31 | padding: 20px 50px; 32 | border-radius: 40px; 33 | cursor: pointer; 34 | } 35 | 36 | .lds-ripple { 37 | display: inline-block; 38 | position: relative; 39 | 40 | width: var(--size); 41 | height: var(--size); 42 | } 43 | 44 | .lds-ripple div { 45 | position: absolute; 46 | border: 4px solid white; 47 | opacity: 1; 48 | border-radius: 50%; 49 | animation: lds-ripple 1s cubic-bezier(0, 0.2, 0.8, 1) infinite; 50 | } 51 | 52 | .lds-ripple div:nth-child(2) { 53 | animation-delay: -0.5s; 54 | } 55 | 56 | @keyframes lds-ripple { 57 | 0% { 58 | top: calc(var(--size) / 2); 59 | left: calc(var(--size) / 2); 60 | width: 0; 61 | height: 0; 62 | opacity: 1; 63 | } 64 | 65 | 100% { 66 | top: 0px; 67 | left: 0px; 68 | width: calc(var(--size) * 0.9); 69 | height: calc(var(--size) * 0.9); 70 | opacity: 0; 71 | } 72 | } 73 | 74 | .hidden { 75 | display: none; 76 | } 77 | -------------------------------------------------------------------------------- /exercises/dad-jokes/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | Dad Jokes 15 | 16 | 17 | 18 |
19 | 26 |
27 |
28 |

Dad Jokes.

29 |
30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /exercises/dad-jokes/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | --size: 20px; 3 | --pink: #f5afc4; 4 | --black: #181718; 5 | --white: #fff; 6 | font-family: "Varela Round", sans-serif; 7 | background: var(--pink); 8 | color: var(--black); 9 | padding: 50px; 10 | } 11 | 12 | .wrapper { 13 | text-align: center; 14 | container-type: inline-size; 15 | } 16 | 17 | .joke { 18 | font-size: 5cqw; 19 | padding: 20px; 20 | font-weight: 900; 21 | text-align: balance; 22 | text-align: center; 23 | } 24 | 25 | button { 26 | font-family: "Varela Round", sans-serif; 27 | background: var(--black); 28 | color: var(--white); 29 | border: 0; 30 | font-size: 30px; 31 | padding: 20px 50px; 32 | border-radius: 40px; 33 | cursor: pointer; 34 | } 35 | 36 | .lds-ripple { 37 | display: inline-block; 38 | position: relative; 39 | 40 | width: var(--size); 41 | height: var(--size); 42 | } 43 | 44 | .lds-ripple div { 45 | position: absolute; 46 | border: 4px solid white; 47 | opacity: 1; 48 | border-radius: 50%; 49 | animation: lds-ripple 1s cubic-bezier(0, 0.2, 0.8, 1) infinite; 50 | } 51 | 52 | .lds-ripple div:nth-child(2) { 53 | animation-delay: -0.5s; 54 | } 55 | 56 | @keyframes lds-ripple { 57 | 0% { 58 | top: calc(var(--size) / 2); 59 | left: calc(var(--size) / 2); 60 | width: 0; 61 | height: 0; 62 | opacity: 1; 63 | } 64 | 65 | 100% { 66 | top: 0px; 67 | left: 0px; 68 | width: calc(var(--size) * 0.9); 69 | height: calc(var(--size) * 0.9); 70 | opacity: 0; 71 | } 72 | } 73 | 74 | .hidden { 75 | display: none; 76 | } 77 | -------------------------------------------------------------------------------- /exercises/focus-machine-start/icons/Airplane.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /exercises/focus-machine-start/icons/Airport.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /exercises/focus-machine-start/icons/Car.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 20 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /exercises/focus-machine-start/icons/Construction.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /exercises/focus-machine-start/icons/Fire.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /exercises/focus-machine-start/icons/Football.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /exercises/focus-machine-start/icons/Highway.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /exercises/focus-machine-start/icons/Laundry.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 11 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /exercises/focus-machine-start/icons/Restaurant.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 9 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /exercises/focus-machine-start/icons/coaster.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /exercises/focus-machine-start/icons/coffee.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /exercises/focus-machine-start/icons/doors.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 10 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /exercises/focus-machine-start/icons/files.txt: -------------------------------------------------------------------------------- 1 | https://svg.wesbos.com/Noise.svg 2 | https://svg.wesbos.com/Airport.svg 3 | https://svg.wesbos.com/Airplane.svg 4 | https://svg.wesbos.com/coaster.svg 5 | https://svg.wesbos.com/Bus.svg 6 | https://svg.wesbos.com/Car.svg 7 | https://svg.wesbos.com/Park.svg 8 | https://svg.wesbos.com/Traffic.svg 9 | https://svg.wesbos.com/Crowd.svg 10 | https://svg.wesbos.com/city.svg 11 | https://svg.wesbos.com/city.svg 12 | https://svg.wesbos.com/School.svg 13 | https://svg.wesbos.com/burger.svg 14 | https://svg.wesbos.com/Fire.svg 15 | https://svg.wesbos.com/Restaurant.svg 16 | https://svg.wesbos.com/Highway.svg 17 | https://svg.wesbos.com/Highway.svg 18 | https://svg.wesbos.com/grocery.svg 19 | https://svg.wesbos.com/Talking.svg 20 | https://svg.wesbos.com/doors.svg 21 | https://svg.wesbos.com/Highway.svg 22 | https://svg.wesbos.com/Football.svg 23 | https://svg.wesbos.com/Laundry.svg 24 | https://svg.wesbos.com/Lobby.svg 25 | https://svg.wesbos.com/Lobby.svg 26 | https://svg.wesbos.com/Lobby.svg 27 | https://svg.wesbos.com/market.svg 28 | https://svg.wesbos.com/market.svg 29 | https://svg.wesbos.com/market.svg 30 | https://svg.wesbos.com/subway.svg 31 | https://svg.wesbos.com/coffee.svg 32 | https://svg.wesbos.com/park.svg 33 | https://svg.wesbos.com/park.svg 34 | https://svg.wesbos.com/party.svg 35 | https://svg.wesbos.com/talking.svg 36 | https://svg.wesbos.com/rain.svg 37 | https://svg.wesbos.com/rain.svg 38 | https://svg.wesbos.com/rain.svg 39 | https://svg.wesbos.com/rain.svg 40 | https://svg.wesbos.com/rain.svg 41 | https://svg.wesbos.com/rain.svg 42 | https://svg.wesbos.com/restaurant.svg 43 | https://svg.wesbos.com/restaurant.svg 44 | https://svg.wesbos.com/river.svg 45 | https://svg.wesbos.com/river.svg 46 | https://svg.wesbos.com/school.svg 47 | https://svg.wesbos.com/waves.svg 48 | https://svg.wesbos.com/waves.svg 49 | https://svg.wesbos.com/mall.svg 50 | https://svg.wesbos.com/speaker.svg 51 | https://svg.wesbos.com/Construction.svg 52 | https://svg.wesbos.com/hockey.svg 53 | https://svg.wesbos.com/traffic.svg 54 | https://svg.wesbos.com/traffic.svg 55 | https://svg.wesbos.com/fountain.svg 56 | https://svg.wesbos.com/waterfall.svg 57 | https://svg.wesbos.com/river.svg 58 | https://svg.wesbos.com/wind.svg 59 | https://svg.wesbos.com/wind.sv 60 | -------------------------------------------------------------------------------- /exercises/focus-machine-start/icons/grocery.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /exercises/focus-machine-start/icons/mall.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /exercises/focus-machine-start/icons/speaker.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 10 | 13 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /exercises/focus-machine-start/icons/subway.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /exercises/focus-machine-start/icons/wind.sv: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

You should end your url with .svg, like /cat.svg

7 | 17 | 18 | -------------------------------------------------------------------------------- /exercises/focus-machine-start/icons/wind.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 12 | 13 | 14 | 18 | 19 | 20 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /exercises/focus-machine-start/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Focus Machine 8 | 9 | 10 | 11 | 12 | 13 |
14 | 31 |
32 |
33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /exercises/focus-machine-start/utils/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/Tasty-TypeScript/9b3c2e8b1480cdc1c80aeac7d9b1789dd0f6b7d1/exercises/focus-machine-start/utils/.gitkeep -------------------------------------------------------------------------------- /exercises/focus-machine/delegate.ts: -------------------------------------------------------------------------------- 1 | export function delegate( 2 | el: Element, 3 | selector: string, 4 | event: keyof WindowEventMap, 5 | handler: EventListener, 6 | options?: boolean | AddEventListenerOptions 7 | ) { 8 | el.addEventListener( 9 | event, 10 | (e: Event) => { 11 | const target = e.target as Element; 12 | // If the thing they clicked on matches the selector passed in, then run the handler! 13 | if (target.matches(selector)) { 14 | handler(e); 15 | } 16 | }, 17 | options 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /exercises/focus-machine/icons/Airplane.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /exercises/focus-machine/icons/Airport.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /exercises/focus-machine/icons/Car.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 20 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /exercises/focus-machine/icons/Construction.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /exercises/focus-machine/icons/Fire.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /exercises/focus-machine/icons/Football.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /exercises/focus-machine/icons/Highway.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /exercises/focus-machine/icons/Laundry.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 11 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /exercises/focus-machine/icons/Restaurant.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 9 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /exercises/focus-machine/icons/coaster.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /exercises/focus-machine/icons/coffee.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /exercises/focus-machine/icons/doors.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 10 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /exercises/focus-machine/icons/files.txt: -------------------------------------------------------------------------------- 1 | https://svg.wesbos.com/Noise.svg 2 | https://svg.wesbos.com/Airport.svg 3 | https://svg.wesbos.com/Airplane.svg 4 | https://svg.wesbos.com/coaster.svg 5 | https://svg.wesbos.com/Bus.svg 6 | https://svg.wesbos.com/Car.svg 7 | https://svg.wesbos.com/Park.svg 8 | https://svg.wesbos.com/Traffic.svg 9 | https://svg.wesbos.com/Crowd.svg 10 | https://svg.wesbos.com/city.svg 11 | https://svg.wesbos.com/city.svg 12 | https://svg.wesbos.com/School.svg 13 | https://svg.wesbos.com/burger.svg 14 | https://svg.wesbos.com/Fire.svg 15 | https://svg.wesbos.com/Restaurant.svg 16 | https://svg.wesbos.com/Highway.svg 17 | https://svg.wesbos.com/Highway.svg 18 | https://svg.wesbos.com/grocery.svg 19 | https://svg.wesbos.com/Talking.svg 20 | https://svg.wesbos.com/doors.svg 21 | https://svg.wesbos.com/Highway.svg 22 | https://svg.wesbos.com/Football.svg 23 | https://svg.wesbos.com/Laundry.svg 24 | https://svg.wesbos.com/Lobby.svg 25 | https://svg.wesbos.com/Lobby.svg 26 | https://svg.wesbos.com/Lobby.svg 27 | https://svg.wesbos.com/market.svg 28 | https://svg.wesbos.com/market.svg 29 | https://svg.wesbos.com/market.svg 30 | https://svg.wesbos.com/subway.svg 31 | https://svg.wesbos.com/coffee.svg 32 | https://svg.wesbos.com/park.svg 33 | https://svg.wesbos.com/park.svg 34 | https://svg.wesbos.com/party.svg 35 | https://svg.wesbos.com/talking.svg 36 | https://svg.wesbos.com/rain.svg 37 | https://svg.wesbos.com/rain.svg 38 | https://svg.wesbos.com/rain.svg 39 | https://svg.wesbos.com/rain.svg 40 | https://svg.wesbos.com/rain.svg 41 | https://svg.wesbos.com/rain.svg 42 | https://svg.wesbos.com/restaurant.svg 43 | https://svg.wesbos.com/restaurant.svg 44 | https://svg.wesbos.com/river.svg 45 | https://svg.wesbos.com/river.svg 46 | https://svg.wesbos.com/school.svg 47 | https://svg.wesbos.com/waves.svg 48 | https://svg.wesbos.com/waves.svg 49 | https://svg.wesbos.com/mall.svg 50 | https://svg.wesbos.com/speaker.svg 51 | https://svg.wesbos.com/Construction.svg 52 | https://svg.wesbos.com/hockey.svg 53 | https://svg.wesbos.com/traffic.svg 54 | https://svg.wesbos.com/traffic.svg 55 | https://svg.wesbos.com/fountain.svg 56 | https://svg.wesbos.com/waterfall.svg 57 | https://svg.wesbos.com/river.svg 58 | https://svg.wesbos.com/wind.svg 59 | https://svg.wesbos.com/wind.sv 60 | -------------------------------------------------------------------------------- /exercises/focus-machine/icons/grocery.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /exercises/focus-machine/icons/mall.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /exercises/focus-machine/icons/speaker.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 10 | 13 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /exercises/focus-machine/icons/subway.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /exercises/focus-machine/icons/wind.sv: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

You should end your url with .svg, like /cat.svg

7 | 17 | 18 | -------------------------------------------------------------------------------- /exercises/focus-machine/icons/wind.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 12 | 13 | 14 | 18 | 19 | 20 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /exercises/focus-machine/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Focus Machine 7 | 8 | 9 | 10 | 11 | 12 |
13 | 30 |
31 |
32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /exercises/focus-machine/utils/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/Tasty-TypeScript/9b3c2e8b1480cdc1c80aeac7d9b1789dd0f6b7d1/exercises/focus-machine/utils/.gitkeep -------------------------------------------------------------------------------- /exercises/focus-machine/utils/dom.ts: -------------------------------------------------------------------------------- 1 | import { Sound } from '../sounds'; 2 | 3 | function generateAudioBlock(sound: Sound): string { 4 | return /* html */ `
5 | 6 | ${sound.name} 7 | 10 | 11 |
`; 12 | } 13 | 14 | export function createAudioHTML(sounds: Sound[]): string { 15 | return sounds.map((sound) => generateAudioBlock(sound)).join(''); 16 | } 17 | -------------------------------------------------------------------------------- /exercises/hotdog-start/hotdog.ts: -------------------------------------------------------------------------------- 1 | // Some element selection to get us going! 2 | const videoEl = document.querySelector('video.webcam'); 3 | const canvasEl = document.querySelector('.still'); 4 | const ctx = canvasEl?.getContext('2d'); 5 | const videoSelect = document.querySelector('select.camera'); 6 | const resultsEl = document.querySelector('.results'); 7 | const startStopButton = 8 | document.querySelector('button.start-stop'); 9 | export {}; 10 | -------------------------------------------------------------------------------- /exercises/hotdog-start/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Hot Dog or Not a HotDog 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 | 16 | 17 | 21 | 22 | Show Me Your Dog 23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 | 32 |
33 |
34 |
35 | 36 | 37 | -------------------------------------------------------------------------------- /exercises/hotdog-start/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hotdog-start", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "author": "Wes Bos", 9 | "license": "ISC", 10 | "dependencies": { 11 | "@tensorflow-models/coco-ssd": "^2.2.3", 12 | "@tensorflow/tfjs": "^4.13.0", 13 | "tsparticles-engine": "^2.12.0", 14 | "tsparticles-preset-confetti": "^2.12.0", 15 | "tsparticles-preset-fireworks": "^2.12.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /exercises/hotdog-start/style.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Luckiest+Guy&display=swap"); 2 | 3 | html { 4 | --red-weiner: #c11107; 5 | --tan-buns: #f59707; 6 | --app-width: 1000px; 7 | background: url(./images/doggy.svg); 8 | font-family: "Luckiest Guy", cursive; 9 | } 10 | 11 | body { 12 | min-height: 100vh; 13 | display: grid; 14 | place-items: center; 15 | margin: 0; 16 | } 17 | 18 | .app { 19 | text-align: center; 20 | margin: 0 auto; 21 | display: inline-block; 22 | max-width: var(--app-width); 23 | position: relative; 24 | } 25 | 26 | .app video { 27 | border-radius: 50px; 28 | width: 100%; 29 | } 30 | 31 | canvas.still { 32 | display: none; 33 | position: absolute; 34 | left: 50%; 35 | top: 50%; 36 | transform: translate(-50%, -50%); 37 | border-radius: 20px; 38 | width: 200px; 39 | box-shadow: 0 0 10px 10px rgba(0, 0, 0, 0.6); 40 | } 41 | 42 | .hotdog { 43 | border: 10px solid yellow; 44 | } 45 | 46 | .not_hotdog { 47 | border: 10px solid red; 48 | } 49 | 50 | .result { 51 | border: 1px solid black; 52 | margin: 1rem; 53 | position: relative; 54 | padding: 10px; 55 | border-radius: 20px; 56 | overflow: hidden; 57 | } 58 | 59 | .confidence { 60 | position: absolute; 61 | top: 0; 62 | left: 0; 63 | width: var(--confidence); 64 | background: var(--tan-buns); 65 | height: 100%; 66 | transition: width 0.2s; 67 | } 68 | 69 | .label { 70 | z-index: 2; 71 | position: relative; 72 | color: black; 73 | font-weight: 900; 74 | } 75 | 76 | .app svg { 77 | fill: none; 78 | position: absolute; 79 | top: 0; 80 | left: 0; 81 | width: 100%; 82 | max-width: var(--app-width); 83 | } 84 | 85 | .app text { 86 | fill: black; 87 | font-size: 46px; 88 | text-shadow: 5px 5px 0 var(--tan-buns), -5px -5px 0 var(--red-weiner); 89 | } 90 | 91 | .controls { 92 | position: relative; 93 | z-index: 2; 94 | } 95 | 96 | button, 97 | select { 98 | border: 0; 99 | padding: 10px; 100 | background: var(--tan-buns); 101 | } 102 | 103 | #tsparticles { 104 | position: absolute; 105 | top: 0; 106 | left: 0; 107 | width: 100%; 108 | height: 100%; 109 | } 110 | 111 | #tsparticles canvas { 112 | position: relative !important; 113 | } 114 | -------------------------------------------------------------------------------- /exercises/hotdog/celebrate.ts: -------------------------------------------------------------------------------- 1 | import { tsParticles } from 'tsparticles-engine'; 2 | import { loadConfettiPreset } from 'tsparticles-preset-confetti'; 3 | 4 | export async function celebrate() { 5 | await loadConfettiPreset(tsParticles); 6 | await tsParticles.load('tsparticles', { 7 | emitters: [ 8 | { 9 | position: { 10 | x: 50, 11 | y: 0, 12 | }, 13 | life: { 14 | duration: 20, // 20 seconds 15 | count: 0, // unlimited 16 | }, 17 | rate: { 18 | quantity: 25, 19 | delay: 0, 20 | }, 21 | }, 22 | ], 23 | preset: 'confetti', 24 | particles: { 25 | color: { 26 | value: ['#1E00FF', '#FF0061', '#E1FF00', '#00FF9E'], 27 | }, 28 | }, 29 | }); 30 | } 31 | 32 | window.celebrate = celebrate; 33 | -------------------------------------------------------------------------------- /exercises/hotdog/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Hot Dog or Not a HotDog 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 | 16 | 20 | 21 | Show Me Your Dog 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
32 |
33 |
34 | 35 | 36 | -------------------------------------------------------------------------------- /exercises/hotdog/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hotdog-working", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "author": "Wes Bos", 9 | "license": "ISC", 10 | "dependencies": { 11 | "@tensorflow-models/coco-ssd": "^2.2.3", 12 | "@tensorflow/tfjs": "^4.13.0", 13 | "tsparticles-engine": "^2.12.0", 14 | "tsparticles-preset-confetti": "^2.12.0", 15 | "tsparticles-preset-fireworks": "^2.12.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /exercises/hotdog/style.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Luckiest+Guy&display=swap"); 2 | 3 | html { 4 | --red-weiner: #c11107; 5 | --tan-buns: #f59707; 6 | --app-width: 1000px; 7 | background: url(./images/doggy.svg); 8 | font-family: "Luckiest Guy", cursive; 9 | } 10 | 11 | body { 12 | min-height: 100vh; 13 | display: grid; 14 | place-items: center; 15 | margin: 0; 16 | } 17 | 18 | .app { 19 | text-align: center; 20 | margin: 0 auto; 21 | display: inline-block; 22 | max-width: var(--app-width); 23 | position: relative; 24 | } 25 | 26 | .app video { 27 | border-radius: 50px; 28 | width: 100%; 29 | } 30 | 31 | canvas.still { 32 | display: none; 33 | position: absolute; 34 | left: 50%; 35 | top: 50%; 36 | transform: translate(-50%, -50%); 37 | border-radius: 20px; 38 | width: 200px; 39 | box-shadow: 0 0 10px 10px rgba(0, 0, 0, 0.6); 40 | } 41 | 42 | .hotdog { 43 | border: 10px solid yellow; 44 | } 45 | 46 | .not_hotdog { 47 | border: 10px solid red; 48 | } 49 | 50 | .result { 51 | border: 1px solid black; 52 | margin: 1rem; 53 | position: relative; 54 | padding: 10px; 55 | border-radius: 20px; 56 | overflow: hidden; 57 | } 58 | 59 | .confidence { 60 | position: absolute; 61 | top: 0; 62 | left: 0; 63 | width: calc(var(--confidence) * 1%); 64 | background: var(--tan-buns); 65 | height: 100%; 66 | transition: width 0.2s; 67 | } 68 | 69 | .label { 70 | z-index: 2; 71 | position: relative; 72 | color: black; 73 | font-weight: 900; 74 | } 75 | 76 | .app svg { 77 | fill: none; 78 | position: absolute; 79 | top: 0; 80 | left: 0; 81 | width: 100%; 82 | max-width: var(--app-width); 83 | } 84 | 85 | .app text { 86 | fill: black; 87 | font-size: 46px; 88 | text-shadow: 5px 5px 0 var(--tan-buns), -5px -5px 0 var(--red-weiner); 89 | } 90 | 91 | .controls { 92 | position: relative; 93 | z-index: 2; 94 | } 95 | 96 | button, 97 | select { 98 | border: 0; 99 | padding: 10px; 100 | background: var(--tan-buns); 101 | } 102 | 103 | #tsparticles { 104 | position: absolute; 105 | top: 0; 106 | left: 0; 107 | width: 100%; 108 | height: 100%; 109 | } 110 | 111 | #tsparticles canvas { 112 | position: relative !important; 113 | } 114 | -------------------------------------------------------------------------------- /finished-exercises/CRUD/fun.ts: -------------------------------------------------------------------------------- 1 | const fields = [ 2 | { 3 | inputType: 'text', 4 | label: 'ID', 5 | name: 'id', 6 | }, 7 | { 8 | inputType: 'text', 9 | label: 'Description', 10 | name: 'description', 11 | }, 12 | { 13 | inputType: 'number', 14 | label: 'Price', 15 | name: 'price', 16 | }, 17 | ] as const; 18 | 19 | type Field = typeof fields[number]; 20 | 21 | type Item = { [K in Field['name']]: string }; 22 | -------------------------------------------------------------------------------- /finished-exercises/CRUD/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | CRUD 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |

CRUD!

17 |

Create × Read × Update × Delete

19 | 33 | 34 | 41 | 42 |
43 |
    44 | 45 |
    46 | 47 |
    48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /finished-exercises/CRUD/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "crud", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "author": "Wes Bos", 9 | "license": "ISC", 10 | "dependencies": { 11 | "uuid": "^8.3.2", 12 | "@types/uuid": "^8.3.4" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /finished-exercises/augmented-reality/database.ts: -------------------------------------------------------------------------------- 1 | export const codes = { 2 | 1: { 3 | name: 'Kitchen Lights', 4 | value: 1, 5 | }, 6 | 2: { 7 | name: 'Stove', 8 | value: 2, 9 | }, 10 | 3: { 11 | name: 'Outdoor', 12 | value: 3, 13 | }, 14 | banana: { 15 | name: '🍌', 16 | value: 'banana', 17 | }, 18 | apple: { 19 | name: '🍎', 20 | value: 'apple', 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /finished-exercises/augmented-reality/fonts/material-symbols.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/Tasty-TypeScript/9b3c2e8b1480cdc1c80aeac7d9b1789dd0f6b7d1/finished-exercises/augmented-reality/fonts/material-symbols.woff -------------------------------------------------------------------------------- /finished-exercises/augmented-reality/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Augmented Reality 8 | 9 | 10 | 11 | 12 | 13 |
    14 |

    Augmented Reality!

    15 | 19 | 20 |
    21 |
    22 | 23 |
    24 | 25 |
    26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /finished-exercises/augmented-reality/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "augmented-reality", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "start": "vite ." 7 | }, 8 | "keywords": [], 9 | "author": "Wes Bos", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@undecaf/barcode-detector-polyfill": "^0.9.9", 13 | "vite": "^2.9.9" 14 | }, 15 | "devDependencies": { 16 | "@originjs/vite-plugin-commonjs": "^1.0.3", 17 | "path-browserify": "^1.0.1" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /finished-exercises/augmented-reality/qrcodes/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/Tasty-TypeScript/9b3c2e8b1480cdc1c80aeac7d9b1789dd0f6b7d1/finished-exercises/augmented-reality/qrcodes/1.png -------------------------------------------------------------------------------- /finished-exercises/augmented-reality/qrcodes/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/Tasty-TypeScript/9b3c2e8b1480cdc1c80aeac7d9b1789dd0f6b7d1/finished-exercises/augmented-reality/qrcodes/2.png -------------------------------------------------------------------------------- /finished-exercises/augmented-reality/qrcodes/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/Tasty-TypeScript/9b3c2e8b1480cdc1c80aeac7d9b1789dd0f6b7d1/finished-exercises/augmented-reality/qrcodes/3.png -------------------------------------------------------------------------------- /finished-exercises/augmented-reality/qrcodes/apple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/Tasty-TypeScript/9b3c2e8b1480cdc1c80aeac7d9b1789dd0f6b7d1/finished-exercises/augmented-reality/qrcodes/apple.png -------------------------------------------------------------------------------- /finished-exercises/augmented-reality/qrcodes/banana.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/Tasty-TypeScript/9b3c2e8b1480cdc1c80aeac7d9b1789dd0f6b7d1/finished-exercises/augmented-reality/qrcodes/banana.png -------------------------------------------------------------------------------- /finished-exercises/augmented-reality/utils/getImageData.ts: -------------------------------------------------------------------------------- 1 | export function getImageData( 2 | media: HTMLImageElement | HTMLVideoElement, 3 | canvas: HTMLCanvasElement 4 | ): ImageData { 5 | const ctx = canvas.getContext('2d'); 6 | if (!ctx) throw new Error('Could not get canvas context'); 7 | ctx.drawImage(media, 0, 0); 8 | return ctx.getImageData(0, 0, media.width, media.height); 9 | } 10 | -------------------------------------------------------------------------------- /finished-exercises/augmented-reality/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import { esbuildCommonjs } from '@originjs/vite-plugin-commonjs'; 3 | 4 | export default defineConfig({ 5 | optimizeDeps: { 6 | esbuildOptions: { 7 | plugins: [esbuildCommonjs(['zbar.wasm'])], 8 | }, 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /finished-exercises/complain-together/another.css: -------------------------------------------------------------------------------- 1 | .thing span { 2 | background: red; 3 | } 4 | -------------------------------------------------------------------------------- /finished-exercises/complain-together/complain.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Complain Together 8 | 9 | 10 | 11 | 12 |
    13 |

    Complainer 2000: complain globally™

    14 |

    You think your gas is expensive?!

    15 | 16 |
    17 | 18 | 19 | 21 | per 22 | 24 |
    25 | That is 26 | _ per ! 28 | 29 |
    30 |
    31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /finished-exercises/complain-together/lib/rates.ts: -------------------------------------------------------------------------------- 1 | import { Rates, RatesByCurrency, CurrencyCode } from './convert'; 2 | 3 | type RateResponse = { 4 | amount: number; 5 | base: CurrencyCode; 6 | date: string; 7 | rates: Rates; 8 | }; 9 | 10 | const rates = {} as RatesByCurrency; 11 | 12 | export async function getRates(baseCurrency: CurrencyCode): Promise { 13 | // First check if we have that cached 14 | if (rates[baseCurrency]) { 15 | console.log(`Using cached rates for ${baseCurrency}`); 16 | return rates[baseCurrency]; 17 | } 18 | // otherr wise fetch the rates 19 | console.info(`Fetching rates for ${baseCurrency}`); 20 | const response = await fetch( 21 | `https://api.frankfurter.app/latest?base=${baseCurrency}` 22 | ); 23 | const json = (await response.json()) as RateResponse; 24 | // cache them 25 | rates[baseCurrency] = json.rates; 26 | rates[baseCurrency][baseCurrency] = 1; 27 | return json.rates; 28 | } 29 | 30 | export function formatMoney(amount: number, currency: CurrencyCode): string { 31 | const formatter = new Intl.NumberFormat('en-US', { 32 | style: 'currency', 33 | currency, 34 | }); 35 | return formatter.format(amount); 36 | } 37 | -------------------------------------------------------------------------------- /finished-exercises/complain-together/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-family: 'Inter', sans-serif; 3 | font-size: 16px; 4 | line-height: 1.5; 5 | color: #000; 6 | background-color: #fff; 7 | --brad: 3px; /* such a brad thing */ 8 | --yellow: #ffc600; 9 | --pink: #FF68B8; 10 | background-image: linear-gradient(160deg, #0093E9 0%, #80D0C7 100%); 11 | background-image: linear-gradient(90deg, #00DBDE 0%, #FC00FF 100%); 12 | height: 100%; 13 | display: grid; 14 | align-content: center; 15 | justify-items: center; 16 | } 17 | 18 | body { 19 | margin: 0; 20 | padding: 20px; 21 | } 22 | 23 | form { 24 | padding: 20px; 25 | border-radius: var(--brad); 26 | font-weight: 900; 27 | } 28 | 29 | form :is(select, input, textarea) { 30 | padding: 10px; 31 | border: 1px solid var(--yellow); 32 | border-radius: var(--brad); 33 | background: none; 34 | box-sizing: border-box; 35 | margin-bottom: 10px; 36 | font-weight: 900; 37 | 38 | } 39 | 40 | form :is(select, input, textarea):focus { 41 | outline: var(--pink) solid 1px; 42 | } 43 | 44 | .complain { 45 | padding: 50px; 46 | border-radius: 20px; 47 | position: relative; 48 | border: 1px solid rgba(255, 255, 255, 0.4); 49 | overflow: hidden; 50 | text-align: center; 51 | } 52 | 53 | .complain:after { 54 | display: block; 55 | content: ''; 56 | background: rgba(255,255,255,0.2); 57 | background-image: linear-gradient(120deg, #fdfbfb 0%, #ebedee 100%); 58 | filter: blur(2px); 59 | position: absolute; 60 | height:120%; 61 | width: 120%; 62 | top: -10%; 63 | left: -10%; 64 | z-index: -1; 65 | opacity: 0.3; 66 | } 67 | 68 | h1 { 69 | margin: 0; 70 | } 71 | -------------------------------------------------------------------------------- /finished-exercises/complain-together/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "strictNullChecks": true, 5 | "noImplicitAny": true, 6 | "lib": [ 7 | "DOM", 8 | "ES2022", 9 | ] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /finished-exercises/complain-together/types.ts: -------------------------------------------------------------------------------- 1 | export interface PageForms extends HTMLCollectionOf { 2 | prices: { 3 | currency: HTMLSelectElement; 4 | unit: HTMLSelectElement; 5 | currencyTo: HTMLSelectElement; 6 | unitTo: HTMLSelectElement; 7 | price: HTMLInputElement; 8 | } & HTMLFormElement; 9 | } 10 | -------------------------------------------------------------------------------- /finished-exercises/compound/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Compound 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
    16 |
    17 |
    18 | 19 | 20 |

    How much will you start with?

    21 |
    22 |
    23 | 24 | 25 |

    What % of return do you expect?

    26 |
    27 |
    28 | 29 | 30 |

    How many years will you invest?

    31 |
    32 |
    33 | 34 | 35 |

    How much will you contribute each period?

    36 |
    37 |
    38 | 39 | 46 |

    How often will you contribute?

    47 |
    48 |
    49 |
    50 | 51 |
    52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /finished-exercises/compound/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "compound", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "compound", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "financial": "^0.1.3" 13 | } 14 | }, 15 | "node_modules/financial": { 16 | "version": "0.1.3", 17 | "resolved": "https://registry.npmjs.org/financial/-/financial-0.1.3.tgz", 18 | "integrity": "sha512-vdv2nx8x+u1bgfcdt/BsKU4Gpqfu9u2485UJoQAs6GqrASszm6QWY7d8H7x968G5rAdV4SU2Nz4JoLTtHUnlcA==", 19 | "engines": { 20 | "node": ">=12" 21 | } 22 | } 23 | }, 24 | "dependencies": { 25 | "financial": { 26 | "version": "0.1.3", 27 | "resolved": "https://registry.npmjs.org/financial/-/financial-0.1.3.tgz", 28 | "integrity": "sha512-vdv2nx8x+u1bgfcdt/BsKU4Gpqfu9u2485UJoQAs6GqrASszm6QWY7d8H7x968G5rAdV4SU2Nz4JoLTtHUnlcA==" 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /finished-exercises/compound/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "compound-wes", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "author": "Wes Bos", 9 | "license": "ISC", 10 | "main": "./compound.ts", 11 | "dependencies": { 12 | "financial": "^0.1.3" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /finished-exercises/dad-jokes/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | Dad Jokes 14 | 15 | 16 | 17 |
    18 | 25 |
    26 |
    27 |

    Dad Jokes.

    28 |
    29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /finished-exercises/dad-jokes/jokes.ts: -------------------------------------------------------------------------------- 1 | const jokeButton = document.querySelector('.getJoke'); 2 | if (!jokeButton) throw new Error('No joke button found'); 3 | const jokeButtonSpan = jokeButton.querySelector('.jokeText'); 4 | const jokeHolder = document.querySelector('.joke p'); 5 | const loader = document.querySelector('.loader'); 6 | 7 | interface JokeResponse { 8 | id: string; 9 | joke: string; 10 | status: number; 11 | } 12 | 13 | const buttonText: string[] = [ 14 | 'Ugh.', 15 | '🤦🏻‍♂️', 16 | 'omg dad.', 17 | 'you are the worst', 18 | 'seriously', 19 | 'stop it.', 20 | 'please stop', 21 | 'that was the worst one', 22 | ]; 23 | 24 | export async function fetchJoke(): Promise { 25 | const response = await fetch('https://icanhazdadjoke.com', { 26 | headers: { 27 | Accept: 'application/json', 28 | }, 29 | }); 30 | return (await response.json()) as JokeResponse; 31 | } 32 | 33 | async function getJoke(): Promise { 34 | // turn loader on 35 | // Here we use ? because if loader is null, it doesnt break the app 36 | loader?.classList.remove('hidden'); 37 | const JokeResponse = await fetchJoke(); 38 | // turn the loader off 39 | loader?.classList.add('hidden'); 40 | return JokeResponse; 41 | } 42 | 43 | function randomItemFromArray(arr: string[], not: string): string { 44 | const item = arr[Math.floor(Math.random() * arr.length)]; 45 | if (item === not) { 46 | console.log('Ahh we used that one last time, look again'); 47 | return randomItemFromArray(arr, not); 48 | } 49 | return item; 50 | } 51 | 52 | async function handleClick() { 53 | const { joke } = await getJoke(); 54 | if (!jokeHolder || !jokeButtonSpan) throw new Error('No joke element found'); 55 | jokeHolder.textContent = joke; 56 | jokeButtonSpan.textContent = randomItemFromArray( 57 | buttonText, 58 | jokeButtonSpan.textContent || '' 59 | ); 60 | } 61 | 62 | jokeButton.addEventListener('click', handleClick); 63 | -------------------------------------------------------------------------------- /finished-exercises/dad-jokes/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | --size: 20px; 3 | --pink: #f5afc4; 4 | --black: #181718; 5 | --white: #fff; 6 | font-family: "Varela Round", sans-serif; 7 | background: var(--pink); 8 | color: var(--black); 9 | padding: 50px; 10 | } 11 | 12 | .wrapper { 13 | text-align: center; 14 | container-type: inline-size; 15 | } 16 | 17 | .joke { 18 | font-size: 5cqw; 19 | padding: 20px; 20 | font-weight: 900; 21 | text-align: balance; 22 | text-align: center; 23 | } 24 | 25 | button { 26 | font-family: "Varela Round", sans-serif; 27 | background: var(--black); 28 | color: var(--white); 29 | border: 0; 30 | font-size: 30px; 31 | padding: 20px 50px; 32 | border-radius: 40px; 33 | cursor: pointer; 34 | } 35 | 36 | .lds-ripple { 37 | display: inline-block; 38 | position: relative; 39 | 40 | width: var(--size); 41 | height: var(--size); 42 | } 43 | 44 | .lds-ripple div { 45 | position: absolute; 46 | border: 4px solid white; 47 | opacity: 1; 48 | border-radius: 50%; 49 | animation: lds-ripple 1s cubic-bezier(0, 0.2, 0.8, 1) infinite; 50 | } 51 | 52 | .lds-ripple div:nth-child(2) { 53 | animation-delay: -0.5s; 54 | } 55 | 56 | @keyframes lds-ripple { 57 | 0% { 58 | top: calc(var(--size) / 2); 59 | left: calc(var(--size) / 2); 60 | width: 0; 61 | height: 0; 62 | opacity: 1; 63 | } 64 | 65 | 100% { 66 | top: 0px; 67 | left: 0px; 68 | width: calc(var(--size) * 0.9); 69 | height: calc(var(--size) * 0.9); 70 | opacity: 0; 71 | } 72 | } 73 | 74 | .hidden { 75 | display: none; 76 | } 77 | -------------------------------------------------------------------------------- /finished-exercises/dragon-drop/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 3 | background-image: linear-gradient(160deg, #0093E9 0%, #80D0C7 100%); 4 | min-height: 100vh; 5 | --blue: hsl(190, 89%, 50%); 6 | --bs: 0 4px 10px 0 hsla(100, 69%, 0%, 0.1); 7 | } 8 | 9 | .items { 10 | list-style: none; 11 | margin: 0; 12 | padding: 0; 13 | max-width: 500px; 14 | margin: 100px auto; 15 | } 16 | 17 | 18 | .items li { 19 | cursor: pointer; 20 | position: relative; 21 | --rotate: 0deg; 22 | --scale: 1; 23 | transform: scale(var(--scale)) rotate(var(--rotate)); 24 | transition: all 0.2s; 25 | outline: 0; 26 | } 27 | 28 | /* adding margin to the li items causes lots of bugs with drag and drop, its best if they kiss eachother. We can add styles inside the li */ 29 | .items li .dragStyles { 30 | padding: 10px; 31 | background: white; 32 | border-radius: 2px; 33 | box-shadow: var(--bs); 34 | margin: 10px; 35 | border: 2px solid white; 36 | } 37 | 38 | .items li:focus .dragStyles { 39 | border-color: #ffc600; 40 | } 41 | 42 | .event { 43 | font-size: 10px; 44 | } 45 | 46 | .items li.drag { 47 | --scale: 1.1; 48 | --rotate: 1deg; 49 | } 50 | .items li.drag + li { 51 | margin-top: 20px; 52 | } 53 | 54 | body { 55 | text-align: center; 56 | } 57 | .assistiveText { 58 | text-align: center; 59 | display: block; 60 | margin: 50px auto; 61 | font-size: 20px; 62 | font-weight: 900; 63 | } 64 | -------------------------------------------------------------------------------- /finished-exercises/dragon-drop/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "strictNullChecks": true, 5 | "lib": [ 6 | "DOM", 7 | "es2020" 8 | ] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /finished-exercises/focus-machine/dom.ts: -------------------------------------------------------------------------------- 1 | import { Sound } from './sounds'; 2 | 3 | /** 4 | * 5 | * @returns Audio HTML String Fragment 6 | */ 7 | export function generateAudioBlock(sound: Sound): string { 8 | return /* html */ ` 9 |
    10 | 11 | ${sound.name} 12 | 15 | 18 |
    19 | `; 20 | } 21 | 22 | export function createAudioHTML(sounds: Sound[]): string { 23 | return sounds.map((sound) => generateAudioBlock(sound)).join(''); 24 | } 25 | 26 | export function handleVolumeChange(e: Event) { 27 | const rangeInput = e.target as HTMLInputElement; 28 | if (!rangeInput) return; 29 | const audioEl = rangeInput 30 | .closest(`#${rangeInput.name}`) 31 | ?.querySelector('audio'); 32 | if (!audioEl) throw new Error('No audio element found'); 33 | audioEl.volume = rangeInput.valueAsNumber; 34 | } 35 | 36 | export function togglePlay(e: Event) { 37 | const button = e.target as HTMLButtonElement; 38 | const audioEl = button.closest('.sound')?.querySelector('audio'); 39 | if (!audioEl) throw new Error('No audio element found'); 40 | if (audioEl.paused) { 41 | audioEl.play(); 42 | } else { 43 | audioEl.pause(); 44 | } 45 | } 46 | export function handlePlayPause(e: Event) { 47 | const el = e.target as HTMLAudioElement; 48 | const soundEl = el.closest('.sound'); 49 | if (!soundEl) throw new Error('No sound element found'); 50 | if (el.paused) { 51 | soundEl.classList.remove('playing'); 52 | soundEl.classList.add('paused'); 53 | return; 54 | } 55 | soundEl.classList.add('playing'); 56 | soundEl.classList.remove('paused'); 57 | } 58 | 59 | export function updateVolumeSlider(e: Event) { 60 | const audioEl = e.target as HTMLAudioElement; 61 | const rangeInput = document.querySelector( 62 | `[name="${audioEl.dataset.id || ''}"]` 63 | ); 64 | if (!rangeInput) throw new Error('No range input found'); 65 | rangeInput.value = audioEl.volume.toString(); 66 | } 67 | -------------------------------------------------------------------------------- /finished-exercises/focus-machine/icons/Airplane.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /finished-exercises/focus-machine/icons/Airport.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /finished-exercises/focus-machine/icons/Car.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 20 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /finished-exercises/focus-machine/icons/Construction.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /finished-exercises/focus-machine/icons/Fire.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /finished-exercises/focus-machine/icons/Football.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /finished-exercises/focus-machine/icons/Highway.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /finished-exercises/focus-machine/icons/Laundry.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 11 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /finished-exercises/focus-machine/icons/Restaurant.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 9 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /finished-exercises/focus-machine/icons/coaster.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /finished-exercises/focus-machine/icons/coffee.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /finished-exercises/focus-machine/icons/doors.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 10 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /finished-exercises/focus-machine/icons/files.txt: -------------------------------------------------------------------------------- 1 | https://svg.wesbos.com/Noise.svg 2 | https://svg.wesbos.com/Airport.svg 3 | https://svg.wesbos.com/Airplane.svg 4 | https://svg.wesbos.com/coaster.svg 5 | https://svg.wesbos.com/Bus.svg 6 | https://svg.wesbos.com/Car.svg 7 | https://svg.wesbos.com/Park.svg 8 | https://svg.wesbos.com/Traffic.svg 9 | https://svg.wesbos.com/Crowd.svg 10 | https://svg.wesbos.com/city.svg 11 | https://svg.wesbos.com/city.svg 12 | https://svg.wesbos.com/School.svg 13 | https://svg.wesbos.com/burger.svg 14 | https://svg.wesbos.com/Fire.svg 15 | https://svg.wesbos.com/Restaurant.svg 16 | https://svg.wesbos.com/Highway.svg 17 | https://svg.wesbos.com/Highway.svg 18 | https://svg.wesbos.com/grocery.svg 19 | https://svg.wesbos.com/Talking.svg 20 | https://svg.wesbos.com/doors.svg 21 | https://svg.wesbos.com/Highway.svg 22 | https://svg.wesbos.com/Football.svg 23 | https://svg.wesbos.com/Laundry.svg 24 | https://svg.wesbos.com/Lobby.svg 25 | https://svg.wesbos.com/Lobby.svg 26 | https://svg.wesbos.com/Lobby.svg 27 | https://svg.wesbos.com/market.svg 28 | https://svg.wesbos.com/market.svg 29 | https://svg.wesbos.com/market.svg 30 | https://svg.wesbos.com/subway.svg 31 | https://svg.wesbos.com/coffee.svg 32 | https://svg.wesbos.com/park.svg 33 | https://svg.wesbos.com/park.svg 34 | https://svg.wesbos.com/party.svg 35 | https://svg.wesbos.com/talking.svg 36 | https://svg.wesbos.com/rain.svg 37 | https://svg.wesbos.com/rain.svg 38 | https://svg.wesbos.com/rain.svg 39 | https://svg.wesbos.com/rain.svg 40 | https://svg.wesbos.com/rain.svg 41 | https://svg.wesbos.com/rain.svg 42 | https://svg.wesbos.com/restaurant.svg 43 | https://svg.wesbos.com/restaurant.svg 44 | https://svg.wesbos.com/river.svg 45 | https://svg.wesbos.com/river.svg 46 | https://svg.wesbos.com/school.svg 47 | https://svg.wesbos.com/waves.svg 48 | https://svg.wesbos.com/waves.svg 49 | https://svg.wesbos.com/mall.svg 50 | https://svg.wesbos.com/speaker.svg 51 | https://svg.wesbos.com/Construction.svg 52 | https://svg.wesbos.com/hockey.svg 53 | https://svg.wesbos.com/traffic.svg 54 | https://svg.wesbos.com/traffic.svg 55 | https://svg.wesbos.com/fountain.svg 56 | https://svg.wesbos.com/waterfall.svg 57 | https://svg.wesbos.com/river.svg 58 | https://svg.wesbos.com/wind.svg 59 | https://svg.wesbos.com/wind.sv 60 | -------------------------------------------------------------------------------- /finished-exercises/focus-machine/icons/grocery.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /finished-exercises/focus-machine/icons/mall.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /finished-exercises/focus-machine/icons/speaker.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 10 | 13 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /finished-exercises/focus-machine/icons/subway.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /finished-exercises/focus-machine/icons/wind.sv: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

    You should end your url with .svg, like /cat.svg

    7 | 17 | 18 | -------------------------------------------------------------------------------- /finished-exercises/focus-machine/icons/wind.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 12 | 13 | 14 | 18 | 19 | 20 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /finished-exercises/focus-machine/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Focus Machine 8 | 9 | 10 | 11 | 12 | 13 |
    14 | 31 |
    32 |
    33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /finished-exercises/focus-machine/utils/delegate.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param el The Parent Element you want to listen on 4 | * @param selector The selector to delegate to 5 | * @param event The event to listen for 6 | * @param handler The handler to call when the event is fired 7 | */ 8 | export function delegate( 9 | el: Element, 10 | selector: string, 11 | event: string, 12 | handler: EventListener, 13 | options?: boolean | AddEventListenerOptions 14 | ) { 15 | // Note to wes: Show this neat trick! 16 | // type WhatIsThisType = Parameters[2] 17 | if (!el) return; // we check for the el here so we can pass in any querySelector value without checking it first 18 | 19 | el.addEventListener( 20 | event, 21 | (e: Event) => { 22 | const target = e.target as Element; 23 | if (target.matches(selector)) { 24 | handler(e); 25 | } 26 | }, 27 | options 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /finished-exercises/hotdog/fireworks.ts: -------------------------------------------------------------------------------- 1 | import { tsParticles } from 'tsparticles-engine'; 2 | import { loadFireworksPreset } from 'tsparticles-preset-fireworks'; 3 | import { loadConfettiPreset } from 'tsparticles-preset-confetti'; 4 | 5 | loadFireworksPreset(tsParticles); 6 | export async function celebrate() { 7 | await loadConfettiPreset(tsParticles); 8 | const x = Math.round(window.innerWidth / 20); 9 | const y = 0; 10 | console.log(x, y); 11 | await tsParticles.load('tsparticles', { 12 | emitters: [ 13 | { 14 | position: { 15 | x, 16 | y, 17 | }, 18 | life: { 19 | duration: 20, // 20 seconds 20 | count: 0, // unlimited 21 | }, 22 | rate: { 23 | quantity: 25, 24 | delay: 0, 25 | }, 26 | }, 27 | ], 28 | preset: 'confetti', 29 | particles: { 30 | color: { 31 | value: ['#1E00FF', '#FF0061', '#E1FF00', '#00FF9E'], 32 | }, 33 | }, 34 | }); 35 | } 36 | -------------------------------------------------------------------------------- /finished-exercises/hotdog/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Hot Dog or Not a HotDog 7 | 8 | 9 | 10 | 11 | 12 | 13 |
    14 |
    15 | 16 | 17 | 21 | 22 | Show Me Your Dog 23 | 24 | 25 | 26 | 27 | 28 | 29 |
    30 | 31 | 32 |
    33 |
    34 |
    35 | 36 | 37 | -------------------------------------------------------------------------------- /finished-exercises/hotdog/ml5.d.ts: -------------------------------------------------------------------------------- 1 | interface ImageClassifier { 2 | classify( 3 | image: HTMLImageElement | HTMLVideoElement | HTMLCanvasElement 4 | ): Promise; 5 | } 6 | 7 | interface ImageClassifierResult { 8 | label: string; 9 | confidence: number; 10 | } 11 | 12 | declare module 'ml5' { 13 | export function imageClassifier( 14 | model: 'MobileNet', 15 | options?: any 16 | ): Promise; 17 | } 18 | -------------------------------------------------------------------------------- /finished-exercises/hotdog/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hotdog", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "author": "Wes Bos", 9 | "license": "ISC", 10 | "dependencies": { 11 | "@tensorflow-models/coco-ssd": "^2.2.3", 12 | "@tensorflow/tfjs": "^4.13.0", 13 | "tsparticles-engine": "^2.12.0", 14 | "tsparticles-preset-confetti": "^2.12.0", 15 | "tsparticles-preset-fireworks": "^2.12.0", 16 | "vite": "^5.0.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /finished-exercises/hotdog/style.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Luckiest+Guy&display=swap"); 2 | 3 | html { 4 | --red-weiner: #c11107; 5 | --tan-buns: #f59707; 6 | --app-width: 1000px; 7 | background: url(./images/doggy.svg); 8 | font-family: "Luckiest Guy", cursive; 9 | } 10 | 11 | body { 12 | min-height: 100vh; 13 | display: grid; 14 | place-items: center; 15 | margin: 0; 16 | } 17 | 18 | .app { 19 | text-align: center; 20 | margin: 0 auto; 21 | display: inline-block; 22 | max-width: var(--app-width); 23 | position: relative; 24 | } 25 | 26 | .app video { 27 | border-radius: 50px; 28 | width: 100%; 29 | } 30 | 31 | canvas.still { 32 | display: none; 33 | position: absolute; 34 | left: 50%; 35 | top: 50%; 36 | transform: translate(-50%, -50%); 37 | border-radius: 20px; 38 | width: 200px; 39 | box-shadow: 0 0 10px 10px rgba(0, 0, 0, 0.6); 40 | } 41 | 42 | .hotdog { 43 | border: 10px solid yellow; 44 | } 45 | 46 | .not_hotdog { 47 | border: 10px solid red; 48 | } 49 | 50 | .result { 51 | border: 1px solid black; 52 | margin: 1rem; 53 | position: relative; 54 | padding: 10px; 55 | border-radius: 20px; 56 | overflow: hidden; 57 | } 58 | 59 | .confidence { 60 | position: absolute; 61 | top: 0; 62 | left: 0; 63 | width: var(--confidence); 64 | background: var(--tan-buns); 65 | height: 100%; 66 | transition: width 0.2s; 67 | } 68 | 69 | .label { 70 | z-index: 2; 71 | position: relative; 72 | color: black; 73 | font-weight: 900; 74 | } 75 | 76 | .app svg { 77 | fill: none; 78 | position: absolute; 79 | top: 0; 80 | left: 0; 81 | width: 100%; 82 | max-width: var(--app-width); 83 | } 84 | 85 | .app text { 86 | fill: black; 87 | font-size: 46px; 88 | text-shadow: 5px 5px 0 var(--tan-buns), -5px -5px 0 var(--red-weiner); 89 | } 90 | 91 | .controls { 92 | position: relative; 93 | z-index: 2; 94 | } 95 | 96 | button, 97 | select { 98 | border: 0; 99 | padding: 10px; 100 | background: var(--tan-buns); 101 | } 102 | 103 | #tsparticles { 104 | position: absolute; 105 | top: 0; 106 | left: 0; 107 | width: 100%; 108 | height: 100%; 109 | } 110 | 111 | #tsparticles canvas { 112 | position: relative !important; 113 | } 114 | -------------------------------------------------------------------------------- /finished-exercises/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 | {%DIRECTORY%} 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /finished-exercises/money/currencies.ts: -------------------------------------------------------------------------------- 1 | export const currencies = { 2 | USD: 'United States Dollar', 3 | AUD: 'Australian Dollar', 4 | BGN: 'Bulgarian Lev', 5 | BRL: 'Brazilian Real', 6 | CAD: 'Canadian Dollar', 7 | CHF: 'Swiss Franc', 8 | CNY: 'Chinese Yuan', 9 | CZK: 'Czech Republic Koruna', 10 | DKK: 'Danish Krone', 11 | GBP: 'British Pound Sterling', 12 | HKD: 'Hong Kong Dollar', 13 | HRK: 'Croatian Kuna', 14 | HUF: 'Hungarian Forint', 15 | IDR: 'Indonesian Rupiah', 16 | ILS: 'Israeli New Sheqel', 17 | INR: 'Indian Rupee', 18 | JPY: 'Japanese Yen', 19 | KRW: 'South Korean Won', 20 | MXN: 'Mexican Peso', 21 | MYR: 'Malaysian Ringgit', 22 | NOK: 'Norwegian Krone', 23 | NZD: 'New Zealand Dollar', 24 | PHP: 'Philippine Peso', 25 | PLN: 'Polish Zloty', 26 | RON: 'Romanian Leu', 27 | RUB: 'Russian Ruble', 28 | SEK: 'Swedish Krona', 29 | SGD: 'Singapore Dollar', 30 | THB: 'Thai Baht', 31 | TRY: 'Turkish Lira', 32 | ZAR: 'South African Rand', 33 | EUR: 'Euro', 34 | }; 35 | 36 | export type Currencies = typeof currencies; 37 | export type CurrencyCode = keyof typeof currencies; 38 | -------------------------------------------------------------------------------- /finished-exercises/money/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Currency Converter 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
    16 |
    17 | 18 | 21 |

    in

    22 | 25 |

    is

    26 |

    $0

    27 |
    28 |
    29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /finished-exercises/money/money.css: -------------------------------------------------------------------------------- 1 | body { 2 | display: grid; 3 | grid-template-rows: 1fr; 4 | justify-content: center; 5 | align-items: center; 6 | min-height: 100vh; 7 | background-image: linear-gradient(to right top, #d16ba5, #c777b9, #ba83ca, #aa8fd8, #9a9ae1, #8aa7ec, #79b3f4, #69bff8, #52cffe, #41dfff, #46eefa, #5ffbf1); 8 | font-family: 'Orelega One', cursive; 9 | font-weight: 100; 10 | padding: 0; 11 | margin: 0; 12 | } 13 | 14 | .app { 15 | max-width: 1200px; 16 | background: rgba(255,255,255,0.85); 17 | border-radius: 20px; 18 | box-shadow: 0 0 10px rgba(0,0,0,0.2); 19 | } 20 | 21 | .app form { 22 | border:0; 23 | display: grid; 24 | grid-template-columns: 1fr; 25 | grid-auto-rows: 1fr; 26 | text-align: center; 27 | padding: 2rem; 28 | grid-gap:0; 29 | align-items: stretch; 30 | font-size: 30px; 31 | } 32 | 33 | 34 | .app :is(button, input, select, textarea) { 35 | margin:0; 36 | text-align: center; 37 | border:0; 38 | font-family: 'Orelega One', cursive; 39 | } 40 | 41 | .app :is(button, input, select, textarea):focus { 42 | outline-color: violet; 43 | } 44 | 45 | .app option { 46 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 47 | font-size: 20px; 48 | } 49 | 50 | .app input, .app select { 51 | font-size: 30px; 52 | background: none; 53 | width: 100%; 54 | } 55 | 56 | .app select { 57 | font-size: 30px; 58 | width: 450px; 59 | } 60 | 61 | 62 | -------------------------------------------------------------------------------- /finished-exercises/repetiton-timer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Stretch timer 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
    17 | 18 |
    19 | 20 | 21 | 22 | 23 |
    24 | 25 | 26 | 27 | 28 |
    29 | 30 |
    31 | 32 |
    33 |
    34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /finished-exercises/repetiton-timer/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "repetiton-timer", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "version": "1.0.0", 9 | "license": "ISC", 10 | "dependencies": { 11 | "waait": "^1.0.5" 12 | } 13 | }, 14 | "node_modules/waait": { 15 | "version": "1.0.5", 16 | "resolved": "https://registry.npmjs.org/waait/-/waait-1.0.5.tgz", 17 | "integrity": "sha512-wp+unA4CpqxvBUKHHv8D86fK4jWByHAWyhEXXVHfVUZfK+16ylpj7hjQ58Z8j9ntu8XNukRQT8Fi5qbyJ8rkyw==" 18 | } 19 | }, 20 | "dependencies": { 21 | "waait": { 22 | "version": "1.0.5", 23 | "resolved": "https://registry.npmjs.org/waait/-/waait-1.0.5.tgz", 24 | "integrity": "sha512-wp+unA4CpqxvBUKHHv8D86fK4jWByHAWyhEXXVHfVUZfK+16ylpj7hjQ58Z8j9ntu8XNukRQT8Fi5qbyJ8rkyw==" 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /finished-exercises/repetiton-timer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "repetiton-timer", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "author": "Wes Bos", 9 | "license": "ISC", 10 | "dependencies": { 11 | "waait": "^1.0.5" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /finished-exercises/repetiton-timer/speech.ts: -------------------------------------------------------------------------------- 1 | const voiceSelect = 2 | document.querySelector('[name="voices"]'); 3 | const speech = new SpeechSynthesisUtterance(); 4 | 5 | export function populateVoices() { 6 | const voices = speechSynthesis.getVoices(); 7 | if (!voiceSelect) return; 8 | 9 | voices.forEach((voice) => { 10 | const option = document.createElement('option'); 11 | option.value = voice.name; 12 | option.innerText = voice.name; 13 | voiceSelect.appendChild(option); 14 | }); 15 | } 16 | 17 | populateVoices(); 18 | 19 | export async function speak(text: string) { 20 | return new Promise((resolve) => { 21 | speech.text = text; 22 | const voice = speechSynthesis 23 | .getVoices() 24 | .find((singleVoice) => singleVoice.name === voiceSelect?.value); 25 | 26 | if (voice) speech.voice = voice; 27 | speechSynthesis.speak(speech); 28 | console.log(speech); 29 | speech.addEventListener('mark', () => console.log('MARKED')); 30 | speech.addEventListener('end', resolve); 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /finished-exercises/voice-changer/audio/wes-talking.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/Tasty-TypeScript/9b3c2e8b1480cdc1c80aeac7d9b1789dd0f6b7d1/finished-exercises/voice-changer/audio/wes-talking.m4a -------------------------------------------------------------------------------- /finished-exercises/voice-changer/effects/anon.ts: -------------------------------------------------------------------------------- 1 | // Based on https://voicechanger.io/transforms/anonymous.js 2 | 3 | function makeDistortionCurve(amount: number) { 4 | const n_samples = 44100; 5 | const curve = new Float32Array(n_samples); 6 | const deg = Math.PI / 180; 7 | let x; 8 | for (let i = 0; i < n_samples; ++i) { 9 | x = (i * 2) / n_samples - 1; 10 | curve[i] = ((3 + amount) * x * 20 * deg) / (Math.PI + amount * Math.abs(x)); 11 | } 12 | return curve; 13 | } 14 | 15 | // TODO: This is a good use case for passing either a stream or an audio element, but not both 16 | 17 | const outputAudioEl = document.querySelector('.output'); 18 | 19 | export async function anonymousTransform( 20 | stream: HTMLAudioElement, 21 | distortionAmount = 15 22 | ) { 23 | const ctx = new AudioContext(); 24 | // Live Voice with a MediaStream object 25 | // const source = ctx.createMediaStreamSource(stream); 26 | // Pre-recorded because my family is wondering what i am doing 27 | const source = ctx.createMediaElementSource(stream); 28 | const destination = ctx.createMediaStreamDestination(); 29 | 30 | if (outputAudioEl) { 31 | outputAudioEl.srcObject = destination.stream; 32 | await outputAudioEl.play(); 33 | } 34 | 35 | // Wave shaper 36 | const waveShaper = ctx.createWaveShaper(); 37 | 38 | waveShaper.curve = makeDistortionCurve(distortionAmount); 39 | 40 | // Wobble 41 | const oscillator = ctx.createOscillator(); 42 | oscillator.frequency.value = 75; 43 | oscillator.type = 'sawtooth'; 44 | oscillator.start(0); 45 | 46 | // Gain 47 | const gainNode = ctx.createGain(); 48 | gainNode.gain.value = 0.0015; 49 | 50 | // Delay 51 | const delay = ctx.createDelay(); 52 | delay.delayTime.value = 0.1; 53 | 54 | const longDelay = ctx.createDelay(); 55 | longDelay.delayTime.value = 1; 56 | 57 | // Plug Everything into each other 58 | 59 | // Anon 60 | 61 | source.connect(delay); 62 | 63 | oscillator.connect(gainNode); 64 | gainNode.connect(delay.delayTime); 65 | delay.connect(longDelay); 66 | // waveShaper.connect(longDelay); 67 | longDelay.connect(ctx.destination); 68 | } 69 | -------------------------------------------------------------------------------- /finished-exercises/voice-changer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Voice Changer 8 | 9 | 10 | 11 | 12 | 13 |
    14 |

    Input:

    15 | 16 |

    Output:

    17 | 18 |
    19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /finished-exercises/voice-changer/voice.ts: -------------------------------------------------------------------------------- 1 | import { anonymousTransform } from './effects/anon'; 2 | 3 | const appEl = document.querySelector('.voiceChanger'); 4 | const audioEl = appEl?.querySelector('audio'); 5 | 6 | async function init() { 7 | console.log('STarting'); 8 | if (!audioEl) { 9 | throw new Error('No audio element found'); 10 | } 11 | const stream = await navigator.mediaDevices.getUserMedia({ 12 | audio: true, 13 | video: false, 14 | }); 15 | audioEl.srcObject = stream; 16 | await audioEl.play(); 17 | 18 | // const audioContext = new AudioContext(); 19 | // TODO: This is a good use case for passing either a stream or an audio element, but not both 20 | // const audioSourceNode = audioContext.createMediaStreamSource(stream); 21 | // const audioSourceNode = audioContext.createMediaElementSource(audioEl); 22 | 23 | await anonymousTransform(audioEl); 24 | } 25 | 26 | init(); 27 | -------------------------------------------------------------------------------- /finished-exercises/weight-conversion/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Convert 9 | 10 | 11 | 12 | 13 |
    14 |

    Convert

    15 |
    16 | 19 | 24 | 25 | 27 |

    28 |
    29 |
    30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /finished-exercises/weight-conversion/src/weights.ts: -------------------------------------------------------------------------------- 1 | export interface Weight { 2 | symbol: string; 3 | name: string; 4 | toKg: number; 5 | } 6 | 7 | export const weights: Weight[] = [ 8 | { 9 | symbol: 'g', 10 | name: 'Grams', 11 | toKg: 0.001, 12 | }, 13 | { 14 | symbol: 'kg', 15 | name: 'Kilograms', 16 | toKg: 1, 17 | }, 18 | { 19 | symbol: 'lbs', 20 | name: 'Pounds', 21 | toKg: 0.453592, 22 | }, 23 | { 24 | symbol: 'ounces', 25 | name: 'Ounces', 26 | toKg: 0.0283495, 27 | }, 28 | { 29 | symbol: 'stone', 30 | name: 'Stone', 31 | toKg: 6.35029, 32 | }, 33 | { 34 | symbol: 'tonne', 35 | name: 'Metric Tonne', 36 | toKg: 1000, 37 | }, 38 | { 39 | symbol: 'ton', 40 | name: "'Merica Ton 🇺🇸🇺🇸🇺🇸", 41 | toKg: 907.185, 42 | }, 43 | ]; 44 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Tasty TypeScript! 8 | 9 | 10 | 11 | {%DIRECTORY%} 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "starter-files", 3 | "version": "1.0.0", 4 | "description": "", 5 | "workspaces": [ 6 | "finished-exercises/*", 7 | "exercises/*" 8 | ], 9 | "scripts": { 10 | "start": "vite", 11 | "lint:fix": "eslint --fix ." 12 | }, 13 | "author": "Wes Bos", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "@babel/eslint-parser": "^7.19.1", 17 | "@babel/preset-react": "^7.18.6", 18 | "@types/node": "^18.8.5", 19 | "@typescript-eslint/eslint-plugin": "^5.40.0", 20 | "@typescript-eslint/parser": "^5.40.0", 21 | "eslint": "^8.25.0", 22 | "eslint-config-airbnb": "^19.0.4", 23 | "eslint-config-airbnb-typescript": "^17.0.0", 24 | "eslint-config-prettier": "^8.5.0", 25 | "eslint-config-wesbos": "^3.1.4", 26 | "eslint-plugin-html": "^7.1.0", 27 | "eslint-plugin-import": "^2.26.0", 28 | "eslint-plugin-jsx-a11y": "^6.6.1", 29 | "eslint-plugin-prettier": "^4.2.1", 30 | "eslint-plugin-react": "^7.31.10", 31 | "eslint-plugin-react-hooks": "^4.6.0", 32 | "prettier": "^2.7.1", 33 | "typescript": "^4.8.4" 34 | }, 35 | "dependencies": { 36 | "@babel/core": "^7.19.3", 37 | "postcss-preset-env": "^7.8.2", 38 | "vite": "^3.1.8", 39 | "vite-plugin-list-directory-contents": "^1.1.1" 40 | }, 41 | "postcss": { 42 | "plugins": { 43 | "postcss-preset-env": { 44 | "stage": 0, 45 | "features": { 46 | "nesting-rules": true 47 | } 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /playground-finished/DOM.ts: -------------------------------------------------------------------------------- 1 | const video = document.createElement('video'); 2 | video.src = 'dog.mp4'; 3 | 4 | const button = document.querySelector('button.go'); 5 | 6 | button?.addEventListener('click', handleClick); 7 | 8 | function handleClick(event: Event) { 9 | console.log(event); 10 | console.log('click'); 11 | } 12 | 13 | button?.addEventListener('click', (event) => { 14 | console.log(event); 15 | }); 16 | 17 | function handlePosition(pos: GeolocationPosition) { 18 | console.log(pos.coords.speed); 19 | } 20 | 21 | function watchUser() { 22 | navigator.geolocation.getCurrentPosition((pos) => { 23 | console.log('Sucecss'); 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /playground-finished/any-vs-unknown.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | let anyVal: any; 3 | let unknownVal: unknown; 4 | 5 | anyVal(); 6 | unknownVal(); 7 | 8 | if(typeof unknownVal === 'function') { 9 | unknownVal(); 10 | } 11 | 12 | anyVal++; 13 | unknownVal++; 14 | 15 | if(typeof unknownVal === 'number') { 16 | unknownVal++; 17 | } 18 | -------------------------------------------------------------------------------- /playground-finished/assertions.ts: -------------------------------------------------------------------------------- 1 | const button = document.createElement('button'); 2 | const videoPlayer = document.querySelector('.player'); 3 | 4 | if (videoPlayer) { 5 | videoPlayer.play(); 6 | } 7 | 8 | button.addEventListener('click', handleEvent); 9 | 10 | function handleEvent(e: MouseEvent) { 11 | const element = e.currentTarget; 12 | if (element instanceof HTMLButtonElement) { 13 | element.textContent = 'You clicked me'; 14 | } 15 | } 16 | 17 | type User = { 18 | name: string; 19 | age: number; 20 | }; 21 | 22 | function createUser() { 23 | let wes = {} as User; 24 | const user = localStorage.getItem('user'); 25 | if (user) { 26 | wes = JSON.parse(user) as User; 27 | } else { 28 | wes = { 29 | name: 'Default', 30 | age: 0, 31 | }; 32 | } 33 | } 34 | 35 | export {}; 36 | -------------------------------------------------------------------------------- /playground-finished/basics.ts: -------------------------------------------------------------------------------- 1 | const name: string = 'wes'; 2 | const age: number = 100; 3 | const isCool: boolean = true; 4 | 5 | const isOld: boolean = age > 100; 6 | const oldestAge: number = Math.max(200, 250); 7 | const shouldCancel = window.confirm('Are you sure you want to exist?'); 8 | 9 | function confirm(message: string): boolean { 10 | return false; 11 | } 12 | 13 | function calculateBill(total: number, tip: number): string { 14 | const billTotal = total + tip; 15 | return `You owe ${billTotal}`; 16 | } 17 | 18 | const whatIsMyTotal = calculateBill(100, 20); 19 | 20 | const names: string[] = ['wes', 'scott']; 21 | const ages: number[] = [12, 54, 34]; 22 | const agesOrNames: (number | string)[] = [12, 54, 34, 'wes']; 23 | 24 | export {}; 25 | -------------------------------------------------------------------------------- /playground-finished/classes-interface.ts: -------------------------------------------------------------------------------- 1 | interface ShowInterface { 2 | title: string; 3 | showNumber: number; 4 | progress: number; 5 | playing: boolean; 6 | toggle: () => boolean; 7 | } 8 | 9 | class Show implements ShowInterface { 10 | progress: number = 0; 11 | 12 | playing: boolean = false; 13 | 14 | constructor(public title: string, public showNumber: number) { 15 | this.title = 'New Show'; 16 | this.showNumber = 100; 17 | } 18 | 19 | toggle() { 20 | this.playing = !this.playing; 21 | return this.playing; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /playground-finished/classes-keywords.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-classes-per-file */ 2 | type ProductType = 'chair' | 'table' | 'sofa'; 3 | 4 | class Product { 5 | // Product Type (public) 6 | public type: ProductType; 7 | 8 | // warehouseId (private) 9 | private warehouseId: string; 10 | 11 | #warehouseIdPrivate: string = 'ABC123'; 12 | 13 | // sku (read only) 14 | readonly sku: string; 15 | 16 | // example (protected) 17 | protected cost: number; 18 | 19 | constructor(type: ProductType, sku: string, warehouseId: string) { 20 | console.log('Created PRodcut'); 21 | this.type = type; 22 | this.warehouseId = warehouseId; 23 | this.sku = sku; 24 | this.cost = 100; 25 | } 26 | 27 | printLabel() { 28 | console.log(this.warehouseId); 29 | console.log(this.#warehouseIdPrivate); 30 | } 31 | } 32 | 33 | class PromoProduct extends Product { 34 | runPromo() { 35 | console.log(this.cost); 36 | console.log(this.warehouseId); 37 | console.log(this.#warehouseIdPrivate); 38 | } 39 | } 40 | 41 | const chair = new Product('chair', 'CHAIR123', 'WH123'); 42 | console.log(chair.type); 43 | console.log(chair.type); 44 | -------------------------------------------------------------------------------- /playground-finished/classes.ts: -------------------------------------------------------------------------------- 1 | // class Sandwich { 2 | // name: string; 3 | 4 | // toppings: string[] = ['Lettuce', 'Cheese']; 5 | 6 | // bitesLeft = 10; 7 | 8 | // constructor(name: string) { 9 | // this.name = name; 10 | // } 11 | 12 | // bite() { 13 | // if (this.bitesLeft <= 0) { 14 | // throw Error('No more bites left!'); 15 | // } 16 | // this.bitesLeft -= 1; 17 | // return this.bitesLeft; 18 | // } 19 | // } 20 | 21 | // const hammySammy = new Sandwich('Ham Sandwich'); 22 | // const bahnMi = new Sandwich('Pork Bahn Mi'); 23 | 24 | class SlideShow { 25 | // el: HTMLElement; 26 | 27 | // speed: number; 28 | 29 | // startingSlide: number; 30 | 31 | constructor( 32 | public el: HTMLElement, 33 | readonly speed: number, 34 | private startingSlide: number = 1 35 | ) { 36 | console.log('Starting'); 37 | // this.el = el; 38 | // this.speed = speed; 39 | // this.startingSlide = startingSlide; 40 | } 41 | 42 | next() { 43 | // this.speed = 300; 44 | } 45 | } 46 | 47 | const myShow = new SlideShow(document.body, 100, 2); 48 | -------------------------------------------------------------------------------- /playground-finished/const-assertions.ts: -------------------------------------------------------------------------------- 1 | const pets = ['snickers', 'sunny'] as const; 2 | 3 | const theme = { 4 | yellow: '#ffc600', 5 | white: '#efefef', 6 | padding: 20, 7 | } as const; 8 | 9 | const name = 'wes'; 10 | const pi = 3.14; 11 | 12 | const element = document.querySelector('.what'); 13 | const element2 = document.querySelector('.what'); 14 | const element3 = document.querySelector('.what') as HTMLInputElement; 15 | const element4 = document.querySelector('.what') as number; 16 | 17 | export {}; 18 | -------------------------------------------------------------------------------- /playground-finished/custom-types.ts: -------------------------------------------------------------------------------- 1 | type PetFood = { 2 | name: string; 3 | treat: boolean; 4 | }; 5 | 6 | type Dog = { 7 | name: string; 8 | age: number; 9 | readonly birthday: Date; 10 | lovesWalks?: boolean; 11 | favFood: PetFood; 12 | }; 13 | 14 | interface Dog2 { 15 | name: string; 16 | age: number; 17 | birthday: Date; 18 | } 19 | 20 | const snickers: Dog = { 21 | age: 8, 22 | name: 'snickers', 23 | birthday: new Date(), 24 | favFood: { 25 | name: 'Scraps', 26 | treat: false, 27 | }, 28 | }; 29 | 30 | const burgers: PetFood = { 31 | name: 'Burgs!', 32 | treat: false, 33 | }; 34 | 35 | const sunny: Dog = { 36 | age: 10, 37 | birthday: new Date(), 38 | name: 'sunny', 39 | favFood: burgers, 40 | }; 41 | 42 | const sentence = `does he love Walks? ${snickers.lovesWalks ? 'YES!' : 'NO'}`; 43 | -------------------------------------------------------------------------------- /playground-finished/date.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Date! 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /playground-finished/date.ts: -------------------------------------------------------------------------------- 1 | const now = new Date(); 2 | 3 | const formatter = new Intl.DateTimeFormat('en-CA', { 4 | dateStyle: 'full', 5 | timeStyle: 'long', 6 | }); 7 | 8 | const formattedTime = formatter.format(now); 9 | 10 | console.log(formattedTime); 11 | -------------------------------------------------------------------------------- /playground-finished/discriminating-union.ts: -------------------------------------------------------------------------------- 1 | interface ParcelCreated { 2 | status: 'created'; 3 | } 4 | interface ParcelShipped { 5 | status: 'shipped'; 6 | location: string; 7 | } 8 | 9 | interface ParcelDelivered { 10 | status: 'delivered'; 11 | pickupLocation: string; 12 | } 13 | 14 | interface ParcelLost { 15 | status: 'lost'; 16 | message: string; 17 | } 18 | 19 | type ParcelState = ParcelCreated | ParcelShipped | ParcelDelivered | ParcelLost; 20 | 21 | function notifyCustomer(parcel: ParcelState) { 22 | if (parcel.status === 'created') { 23 | console.log('your shipment is created'); 24 | } else if (parcel.status === 'shipped') { 25 | console.log('your shipment is Shipped'); 26 | } else if (parcel.status === 'delivered') { 27 | console.log('your shipment is Delivered'); 28 | } else if (parcel.status === 'lost') { 29 | console.log('You are out of luck'); 30 | } else { 31 | const thisSHouldNeverHappen: never = parcel; 32 | } 33 | } 34 | 35 | function notifyCustomer2(parcel: ParcelState) { 36 | switch (parcel.status) { 37 | case 'created': { 38 | console.log('your shipment is created'); 39 | break; 40 | } 41 | case 'shipped': { 42 | console.log('your shipment is created'); 43 | break; 44 | } 45 | case 'delivered': { 46 | console.log('your shipment is created'); 47 | break; 48 | } 49 | case 'lost': { 50 | console.log('your shipment is created'); 51 | break; 52 | } 53 | default: { 54 | console.log('This should never happen'); 55 | const thisSHouldNeverHappen: never = parcel; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /playground-finished/enums.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /playground-finished/enums.ts: -------------------------------------------------------------------------------- 1 | const select = document.querySelector('select'); 2 | enum WeightUnit { 3 | g = 'grams', 4 | kg = 'kilograms', 5 | pounds = 'pounds', 6 | } 7 | 8 | enum Direction { 9 | UP = 1, 10 | DOWN, 11 | LEFT, 12 | RIGHT, 13 | } 14 | 15 | console.log(Direction); 16 | 17 | function move(direction: Direction) { 18 | console.log('You are moving', direction); 19 | } 20 | 21 | move(Direction.DOWN); 22 | 23 | function convertWeight(value: number, unit: WeightUnit) { 24 | return 100; 25 | } 26 | 27 | function populateWeights() { 28 | if (!select) return; 29 | const html = Object.entries(WeightUnit).map( 30 | ([key, val]) => `` 31 | ); 32 | select.innerHTML = html.join(''); 33 | } 34 | 35 | populateWeights(); 36 | 37 | convertWeight(100, WeightUnit.kg); 38 | 39 | enum FontWeights { 40 | light = 100, 41 | normal = 400, 42 | bold = 700, 43 | heavy = 900, 44 | } 45 | 46 | console.log(FontWeights); 47 | 48 | export {}; 49 | -------------------------------------------------------------------------------- /playground-finished/error-handling-promises.ts: -------------------------------------------------------------------------------- 1 | import { wait } from './promises'; 2 | 3 | type User = { 4 | name: string; 5 | cool: boolean; 6 | }; 7 | 8 | async function getUser(id: number): Promise { 9 | await wait(0); 10 | if (id === 69) { 11 | throw new Error('User Now Found'); 12 | } 13 | return { 14 | name: 'Wes bos', 15 | cool: true, 16 | }; 17 | } 18 | 19 | async function collect( 20 | promise: Promise 21 | ): Promise<[error: null, data: T] | [error: Error, data: null]> { 22 | try { 23 | const data = await promise; 24 | return [null, data]; // [null, User] 25 | } catch (err) { 26 | return [err as Error, null]; // [Error, null] 27 | } 28 | } 29 | 30 | const [userError, user] = await collect(getUser(123)); 31 | if (userError) { 32 | console.log(userError); 33 | } else { 34 | console.log(user); 35 | } 36 | 37 | async function handleUserSave() { 38 | const [userError, user] = await collect(getUser(123)); 39 | if (userError) { 40 | console.log('ERROR! 404'); 41 | return; 42 | } 43 | // HAPPY PATH 44 | user.cool; // Exists for sure! 45 | } 46 | -------------------------------------------------------------------------------- /playground-finished/extending-and-merging.ts: -------------------------------------------------------------------------------- 1 | // as a type 2 | type Food = { 3 | expiry: Date; 4 | name: string; 5 | }; 6 | 7 | type Pizza = Food & { 8 | toppings: string[]; 9 | }; 10 | 11 | // With Interfaces 12 | interface FoodInterface { 13 | expiry: Date; 14 | name: string; 15 | } 16 | 17 | interface PizzaInterface extends FoodInterface { 18 | toppings: string[]; 19 | } 20 | 21 | const myPizza: Pizza = { 22 | toppings: ['pepperoni'], 23 | expiry: new Date(Date.now() + 1000 * 60 * 60 * 24 * 5), 24 | name: 'Peppy Pig', 25 | }; 26 | 27 | const myPizzaI: PizzaInterface = { 28 | toppings: ['pepperoni'], 29 | expiry: new Date(Date.now() + 1000 * 60 * 60 * 24 * 5), 30 | name: 'Peppy Pig', 31 | }; 32 | 33 | interface PizzaInterface2 extends Food { 34 | toppings: string[]; 35 | } 36 | 37 | type PizzaType = PizzaInterface & { 38 | toppings: string[]; 39 | }; 40 | -------------------------------------------------------------------------------- /playground-finished/filtering.ts: -------------------------------------------------------------------------------- 1 | type Dog = { 2 | name: string; 3 | barks: boolean; 4 | }; 5 | 6 | type Cat = { 7 | name: string; 8 | meows: boolean; 9 | }; 10 | 11 | type Pet = Dog | Cat; 12 | const myPets: (Dog | Cat)[] = [ 13 | { name: 'Rex', barks: true }, 14 | { name: 'Fluffy', meows: true }, 15 | ]; 16 | 17 | function isDog(pet: Pet): pet is Dog { 18 | return 'barks' in pet; 19 | } 20 | 21 | const myDogs = myPets.filter((pet): pet is Dog => 'barks' in pet); 22 | // const myDogs = myPets.filter(isDog); 23 | 24 | for (const dog of myDogs) { 25 | console.log(dog.barks); 26 | } 27 | 28 | const values = [1, 2, '3']; 29 | const numbers = values.filter( 30 | (value): value is number => typeof value === 'number' 31 | ); 32 | 33 | const myPromises = [ 34 | fetch('https://wesbos.com'), 35 | fetch('https://api.github.com/user/wesbos'), 36 | fetch('https://brokenapi.net'), 37 | ]; 38 | 39 | const allResponses = await Promise.allSettled(myPromises); 40 | 41 | const data = allResponses.filter( 42 | (response): response is PromiseFulfilledResult => 43 | response.status === 'fulfilled' 44 | ); 45 | 46 | data.forEach(async (response) => { 47 | const data = await response.value.json(); 48 | }); 49 | 50 | export {}; 51 | -------------------------------------------------------------------------------- /playground-finished/function-overloading.ts: -------------------------------------------------------------------------------- 1 | type BillOptions = { 2 | total: number; 3 | gst: number; 4 | pst: number; 5 | }; 6 | 7 | function calculateBill(options: BillOptions): number; 8 | function calculateBill(total: number): number; 9 | function calculateBill(total: number, gst: number, pst: number): number; 10 | function calculateBill( 11 | totalOrOptions: number | BillOptions, 12 | gst?: number, 13 | pst?: number 14 | ) { 15 | if (typeof total === 'number') { 16 | } 17 | const tip = 0.15; 18 | if (gst && pst) { 19 | return total + total * gst + total * pst + total * tip; 20 | } 21 | return total + total * tip; 22 | } 23 | 24 | calculateBill(100); 25 | calculateBill(100, 0.06, 0.07); 26 | calculateBill(100, 0.06); // Should Error! 27 | 28 | type Callback = (error: Error | null, result: ReturnType) => void; 29 | type User = { 30 | name: string; 31 | id: string; 32 | age: number; 33 | }; 34 | async function getUser(id: string): Promise; 35 | function getUser(id: string, callback: Callback): void; 36 | async function getUser(id: string, callback?: Callback) { 37 | const user: User = { 38 | name: 'wes', 39 | age: 100, 40 | id: 'abc123', 41 | }; 42 | if (callback) { 43 | return callback(null, user); 44 | } 45 | return user; 46 | } 47 | 48 | const user = await getUser('abc123'); 49 | getUser('abc123').then((user) => { 50 | console.log(user.age); 51 | }); 52 | 53 | getUser('abc123', (err, myUser) => { 54 | console.log(myUser); 55 | }); 56 | 57 | export {}; 58 | -------------------------------------------------------------------------------- /playground-finished/im-vs-ex.ts: -------------------------------------------------------------------------------- 1 | const age = 10; 2 | const age2: number = 10; 3 | 4 | const name = 'wes'; 5 | const name2: string = 'wes'; 6 | 7 | const total = 10 + 10; 8 | 9 | const areYouSure = window.confirm('Are ya sure?'); 10 | const yourName = prompt('what is your name?'); 11 | const welcome = `Hello ${yourName || 'Partner'}`; 12 | 13 | const scores: number[] = [1, 2, 3, 4, 5]; 14 | scores.push(6); 15 | scores.push(7); 16 | 17 | function createPerson(firstName: string) { 18 | return { 19 | legs: 2, 20 | heads: 1, 21 | hair: 'blonde', 22 | name: firstName.toLowerCase(), 23 | }; 24 | } 25 | 26 | const wes = createPerson('WES'); 27 | 28 | export {}; 29 | -------------------------------------------------------------------------------- /playground-finished/import-export/existing.d.ts: -------------------------------------------------------------------------------- 1 | declare function calculateBill(total: number, tip: number, tax: number): number; 2 | 3 | export interface User { 4 | name: string; 5 | age: number; 6 | } 7 | 8 | declare const user: User; 9 | 10 | declare const TOTAL_PER_PAGE: number; 11 | 12 | type MyCoolCompany = { 13 | showHelper: (selector?: string) => void; 14 | }; 15 | 16 | declare global { 17 | interface Window { 18 | myCoolCompany: MyCoolCompany; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /playground-finished/import-export/existing.js: -------------------------------------------------------------------------------- 1 | // function 2 | export function calculateBill(total, tip, tax) { 3 | return total + tip * total + tax * total; 4 | } 5 | // Object 6 | export const user = { 7 | name: 'John', 8 | age: 30, 9 | }; 10 | 11 | // variable 12 | export const TOTAL_PER_PAGE = 10; 13 | -------------------------------------------------------------------------------- /playground-finished/import-export/typing.ts: -------------------------------------------------------------------------------- 1 | import { Burger } from './utils'; 2 | import { calculateBill, user, TOTAL_PER_PAGE } from './existing'; 3 | 4 | const myBurger: Burger = { 5 | lettuce: true, 6 | patties: 999, 7 | }; 8 | 9 | window.myCoolCompany.showHelper(); 10 | window.myCoolCompany.showHelper('#asdfs'); 11 | 12 | console.log(myBurger); 13 | -------------------------------------------------------------------------------- /playground-finished/import-export/utils.ts: -------------------------------------------------------------------------------- 1 | export type Burger = { 2 | patties: number; 3 | lettuce: boolean; 4 | }; 5 | 6 | export class User { 7 | constructor() {} 8 | } 9 | 10 | function connectDB() { 11 | console.log('Connecting to Database!'); 12 | } 13 | 14 | connectDB(); 15 | -------------------------------------------------------------------------------- /playground-finished/jsdoc.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-classes-per-file */ 2 | class Product { 3 | constructor( 4 | public type: string, 5 | public sku: string, 6 | private warehouseId: number 7 | ) { 8 | console.log('Created PRodcut'); 9 | } 10 | 11 | /** 12 | * @deprecated This will be removed in the next version. 13 | */ 14 | printLabel() { 15 | console.log(`Printing label for ${this.type} ${this.sku}`); 16 | } 17 | } 18 | 19 | const chair = new Product('chair', 'CHAIR123', 123); 20 | chair.printLabel(); 21 | 22 | // A Function 23 | 24 | /** 25 | * An example chair used for testing 26 | * @deprecated This will be removed in the next version. Create a {@link Product} instead. 27 | */ 28 | const chair1 = { type: 'chair', sku: 'CHAIR123', warehouseId: 'WH123' }; 29 | 30 | console.log(chair); 31 | 32 | class ProductNotFound extends Error { 33 | constructor( 34 | public readonly message: string, 35 | public readonly productId: string 36 | ) { 37 | super(message); 38 | } 39 | } 40 | 41 | /** 42 | * Description Anywhere! 43 | * @description Get a list of products. 44 | * @todo Migrate to new {@link Product} class. 45 | * @param category - The product category. 46 | * @param sort - The sort order. `ASC` most recent firstm `DESC` most recent last. 47 | * @fires {@link Product} When the product is created. 48 | * @throws {ProductNotFound} - if the product is not found. 49 | * @example ```ts 50 | * const products = getPromoProducts('123'); 51 | * ``` 52 | */ 53 | function getPromoProducts(category: string, sort?: 'ASC' | 'DESC') {} 54 | 55 | try { 56 | getPromoProducts('123'); 57 | } catch (e) { 58 | const error = e as ProductNotFound; 59 | error.productId 60 | } 61 | 62 | export { Product, ProductNotFound, getPromoProducts }; 63 | -------------------------------------------------------------------------------- /playground-finished/looping.ts: -------------------------------------------------------------------------------- 1 | import { bands, Band } from '../test-data/bands'; 2 | 3 | // forEach 4 | bands.forEach((band) => { 5 | console.log(band.members.at(1)?.instrument); 6 | }); 7 | 8 | function logBand(band: Band) { 9 | console.log(band.name); 10 | } 11 | // map 12 | const bandNames = bands.map((band): string | number => { 13 | if (band.name.startsWith('w')) return 777; 14 | return band.name; 15 | }); 16 | 17 | // for of 18 | for (const band of bands) { 19 | console.log(band); 20 | } 21 | 22 | const myObj = { 23 | name: 'john', 24 | age: 30, 25 | 777: true, 26 | }; 27 | 28 | const keys = Object.keys(myObj); 29 | const values = Object.values(myObj); 30 | const entries = Object.entries(myObj); 31 | 32 | for (const [myKey, myVal] of Object.entries(myObj)) { 33 | console.log(myKey); 34 | console.log(myVal); 35 | } 36 | -------------------------------------------------------------------------------- /playground-finished/mapped.ts: -------------------------------------------------------------------------------- 1 | type CourseCode = 'RFB' | 'TTS' | 'ARG' | 'WTF'; 2 | 3 | type CourseInfo = { 4 | name: string; 5 | price: number; 6 | courseCode: CourseCode; 7 | }; 8 | 9 | type CourseOptions = { 10 | [key in CourseCode]?: CourseInfo & { courseCode: key }; 11 | }; 12 | 13 | const courseOptions: CourseOptions = { 14 | ARG: { 15 | name: 'Advanced React and GRaphQL', 16 | courseCode: 'ARG', 17 | price: 10000000, 18 | }, 19 | }; 20 | 21 | // Record Types 22 | type CourseOptions2 = Partial>; 23 | 24 | const myCourseData: CourseOptions2 = { 25 | ARG: { 26 | name: 'Advanced React and GRaphQL', 27 | courseCode: 'ARG', 28 | price: 10000000, 29 | }, 30 | }; 31 | 32 | // metadata 33 | type MetaData = Record; 34 | 35 | const myMeta: MetaData = { 36 | dog: 'snickers', 37 | course: myCourseData.ARG, 38 | }; 39 | 40 | function saveAnyData(anyDatathatTheyGiveus: MetaData) { 41 | console.log(anyDatathatTheyGiveus); 42 | } 43 | 44 | saveAnyData('asdfs'); 45 | saveAnyData(12324); 46 | 47 | saveAnyData({ 48 | [Symbol('wes')]: 'bos', 49 | [Symbol('wes')]: 'terhoff', 50 | }); 51 | -------------------------------------------------------------------------------- /playground-finished/nothing.ts: -------------------------------------------------------------------------------- 1 | type Person = { 2 | name?: string; 3 | age: number | null; 4 | }; 5 | 6 | const wes: Person = { 7 | age: 100, 8 | }; 9 | 10 | wes.age = undefined; 11 | wes.age = null; 12 | 13 | function getIceCream() { 14 | return 'Chocolate'; 15 | } 16 | 17 | const myIceCream = void (1 === 1); 18 | 19 | async function goGetIceCream() { 20 | return 'ice cream'; 21 | } 22 | 23 | const result = void goGetIceCream(); 24 | 25 | const people = [ 26 | { name: 'wes', age: 100 }, 27 | { name: 'Scott', age: 200 }, 28 | ]; 29 | 30 | const person = people.find((per) => per.name === 'wes'); 31 | console.log(person.name); 32 | if (person) { 33 | person.name.toUpperCase(); 34 | } 35 | person.name.toUpperCase(); 36 | 37 | function failJSON(): never { 38 | console.log(`This JSON parse failed`); 39 | throw new Error('Shoot! JSON parse failed!'); 40 | } 41 | 42 | const maybeJSON = 'sdlkfgjsdlkfgjldsfg'; 43 | try { 44 | const result = JSON.parse(maybeJSON); 45 | // ... 46 | } catch (e) { 47 | failJSON(); 48 | } 49 | 50 | // Inifinte Loop 51 | 52 | function inifiteLoop(): never { 53 | while (true) { 54 | const result2 = prompt('Add number to DB?'); 55 | console.log(`Saving to DB...`); 56 | // ... 57 | } 58 | } 59 | 60 | interface Basic { 61 | a: string; 62 | } 63 | 64 | interface WithB extends Basic { 65 | b: string; 66 | c?: never; 67 | } 68 | 69 | interface WithC extends Basic { 70 | b?: never; 71 | c: string; 72 | } 73 | 74 | function withLetter(letters: WithB | WithC) { 75 | return letters.a + letters.b + letters.c; 76 | } 77 | 78 | withLetter({ 79 | a: 'A', 80 | b: 'b', 81 | }); 82 | withLetter({ 83 | a: 'A', 84 | c: 'c', 85 | b: 'b', 86 | }); 87 | 88 | function doSomething(value: string | number) { 89 | if (typeof value === 'string') { 90 | console.log('ITS A STRING'); 91 | } else if (typeof value === 'number') { 92 | console.log('ITS A number'); 93 | } else { 94 | value; 95 | } 96 | } 97 | 98 | export {}; 99 | -------------------------------------------------------------------------------- /playground-finished/promises.ts: -------------------------------------------------------------------------------- 1 | function giveMeAString(): string { 2 | return 'hello'; 3 | } 4 | 5 | function giveMeAStringAfter(ms: number): Promise { 6 | return new Promise((resolve, reject) => { 7 | setTimeout(() => resolve('Hello'), ms); 8 | }); 9 | } 10 | 11 | giveMeAStringAfter(200).then((result) => { 12 | console.log(result); 13 | }); 14 | 15 | export function wait(ms: number): Promise { 16 | return new Promise((resolve) => { 17 | setTimeout(() => resolve(), ms); 18 | }); 19 | } 20 | 21 | interface BlogPost { 22 | title: string; 23 | id: string; 24 | body: string; 25 | date: Date; 26 | } 27 | 28 | async function getPost(id: string): Promise { 29 | await wait(200); // Mimick a network call 30 | return { 31 | body: 'Hello World', 32 | date: new Date(), 33 | id: 'abc123', 34 | title: 'Sweet Blog Post', 35 | }; 36 | } 37 | 38 | const myBlogPost = await getPost('abc123'); 39 | 40 | type Episode = { 41 | number: number; 42 | title: string; 43 | date: number; 44 | url: string; 45 | slug: string; 46 | html: string; 47 | notesFile: string; 48 | displayDate: string; 49 | displayNumber: string; 50 | }; 51 | 52 | async function getShow(showNumber: number): Promise { 53 | const response = await fetch(`https://syntax.fm/api/shows/${showNumber}`); 54 | const show = (await response.json()) as Episode; 55 | return show; 56 | } 57 | 58 | async function fetchData( 59 | resource: RequestInfo | URL, 60 | options?: RequestInit 61 | ): Promise { 62 | const response = await fetch(resource, options); 63 | const data = (await response.json()) as DataType; 64 | return data; 65 | } 66 | 67 | const myDog = await fetchData('https://dogz.net'); 68 | 69 | export {}; 70 | -------------------------------------------------------------------------------- /playground-finished/satisfies.ts: -------------------------------------------------------------------------------- 1 | type Setting = string | number | { [key: string]: Setting } | Setting[]; 2 | type Settings = Record; 3 | 4 | function getData() { 5 | return { you: 'good' }; 6 | } 7 | 8 | const mySettings = { 9 | title: `Wes' Website`, 10 | data: getData(), 11 | size: 200, 12 | overrides: [{ 'font-size': '20px' }], 13 | styleConfig: { 14 | color: 'red', 15 | }, 16 | } satisfies Settings; 17 | 18 | 19 | // Valid Properties 20 | mySettings.title = 'New title'; 21 | console.log(mySettings.size); 22 | // Invalid Properties 23 | console.log(mySettings.scott); 24 | mySettings.limt = 200; 25 | 26 | mySettings.size.toLocaleString(); 27 | 28 | mySettings.overrides.at(0)?.['font-size']; 29 | 30 | console.log(metaData.title); 31 | console.log(metaData.doesntExist); 32 | 33 | export {}; 34 | -------------------------------------------------------------------------------- /playground-finished/template-literal-types.ts: -------------------------------------------------------------------------------- 1 | type Shippers = 'UPS' | 'FedEx' | 'DHL'; 2 | type TrackingTypes = 'Overnight' | 'Priority' | 'Economy'; 3 | 4 | type ShippingMethod = `${Shippers}-${TrackingTypes}`; 5 | 6 | type Shipment = { 7 | orderId: string; 8 | shippingMethod: ShippingMethod; 9 | }; 10 | 11 | const myShipment: Shipment = { 12 | orderId: 'abc123', 13 | shippingMethod: 'FedEx-Overnight', 14 | }; 15 | 16 | type ColorName = 17 | | 'red' 18 | | 'green' 19 | | 'purple' 20 | | 'pink' 21 | | 'blue' 22 | | 'orange' 23 | | 'yellow'; 24 | 25 | type Weights = 50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800; 26 | 27 | type Color = `${ColorName}-${Weights}`; 28 | 29 | type Digit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9; 30 | type PhoneNumber = `${Digit}${Digit}${Digit}-${Digit}`; 31 | 32 | const myNumber: PhoneNumber = '000-0'; 33 | -------------------------------------------------------------------------------- /playground-finished/template-string-types.ts: -------------------------------------------------------------------------------- 1 | type Color = 'blue' | 'green' | 'red' | 'orange'; 2 | type Weight = 50 | 100 | 200 | 300 | 400; 3 | 4 | type ColorWeight = `${Color}-${Weight}`; 5 | 6 | const myValut: ColorWeight = ''; 7 | 8 | type Course = 'RFB' | 'TTS' | 'JS30'; 9 | 10 | type Product = `${Course}${1 | 2 | 3}`; 11 | 12 | type Purchase = { 13 | name: string; 14 | amount: number; 15 | product: Product; 16 | }; 17 | 18 | const myProduct: Purchase = { 19 | product: '', 20 | }; 21 | 22 | type ShippingMethod = `${'UPS' | 'FedEx' | 'DHL'} - ${'OVERNIGHT' | 'ECONOMY'}`; 23 | 24 | type InvoiceNumber = `INV-${string}`; 25 | const myInvoice: InvoiceNumber = 'INV-1234'; 26 | const myInvoice2: InvoiceNumber = 'INV-1234'; 27 | 28 | export {}; 29 | -------------------------------------------------------------------------------- /playground-finished/tuple-response.ts: -------------------------------------------------------------------------------- 1 | type User = { 2 | name: string; 3 | id: number; 4 | featureFlags: string[]; 5 | }; 6 | 7 | function queryDB(id: number): User { 8 | // Mimic a DB either reutnring some data, OR throwing an error 9 | if (Math.random() > 0.5) { 10 | throw new Error(`User ${id} not found!`); 11 | } 12 | return { 13 | name: 'Name', 14 | id, 15 | featureFlags: ['Beta'], 16 | }; 17 | } 18 | 19 | type UserResponse = [Error, undefined] | [undefined, User]; 20 | function getUser(id: number): UserResponse { 21 | try { 22 | const user = queryDB(id); 23 | return [undefined, user]; 24 | } catch (err) { 25 | return [err as Error, undefined]; 26 | } 27 | } 28 | 29 | const [error, user] = getUser(123); 30 | if (error) { 31 | console.log(error); 32 | document.body.textContent = error.message; 33 | // display the error 34 | } else { 35 | document.body.textContent = `Hello ${user.name}! Your id is ${user.id}`; 36 | console.log(user); 37 | } 38 | // go ahead and use the user 39 | 40 | export {}; 41 | -------------------------------------------------------------------------------- /playground-finished/tuples.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Tuple Types 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /playground-finished/tuples.ts: -------------------------------------------------------------------------------- 1 | type Person = { 2 | name: string; 3 | age: number; 4 | }; 5 | 6 | const people: Person[] = [ 7 | { name: 'Wes', age: 1 }, 8 | { name: 'Scott', age: 66 }, 9 | ]; 10 | 11 | const scores: number[] = [1, 2, 3]; 12 | const scores2 = [1, 2, 3]; 13 | 14 | type PlayerStats = [name: string, age: number, cool: boolean]; 15 | const stats: PlayerStats = ['wes bos', 99, true]; 16 | 17 | const line = 'wesbos,99,true'; 18 | const [playerName, age, cool] = line.split(','); 19 | const statsFromCSV: PlayerStats = [ 20 | playerName, 21 | Number(age), 22 | cool.toLowerCase() === 'true', 23 | ]; 24 | 25 | type KeyValPair = [city: string, population: number]; 26 | 27 | const data: KeyValPair[] = [ 28 | ['Hamilton', 2000], 29 | ['toronto', 50000], 30 | ]; 31 | 32 | const population = Object.fromEntries(data); 33 | 34 | // Destructuring Tuples 35 | function myState( 36 | initialValue: StateType 37 | ): [StateType, (newState: StateType) => void] { 38 | let state = initialValue; 39 | function updateState(updatedValue: StateType) { 40 | state = updatedValue; 41 | } 42 | return [state, updateState]; 43 | } 44 | 45 | const [myAge, setAge] = myState(100); 46 | console.log(myAge); 47 | const [myName, updateName] = myState('wes'); 48 | type Dog = { 49 | name: string; 50 | age: number; 51 | }; 52 | const [dog, updateDog] = myState({ name: 'snickers', age: 9 }); 53 | 54 | // Branded Tuples 55 | type Lat = number & {}; 56 | type Lng = number & { __kind: 'lng' }; 57 | export type LatLng = [Lat, Lng]; 58 | const latLng: LatLng = [42.342, 84.234234 as Lng]; 59 | const [myLat, myLng] = latLng; 60 | const what = latLng[0]; 61 | const areYou = latLng[1]; 62 | 63 | export {}; 64 | -------------------------------------------------------------------------------- /playground-finished/type-guards.ts: -------------------------------------------------------------------------------- 1 | type Pizza = { 2 | toppings: string[]; 3 | size: 'm' | 'l'; 4 | name: string; 5 | }; 6 | 7 | function displayValue(price: number | string) { 8 | if (typeof price === 'number') { 9 | price; 10 | } 11 | } 12 | 13 | function displayPizza(pizza: Pizza | string) { 14 | if('toppings' in pizza && pizza?.toppings?.length) 15 | } 16 | 17 | function handleResult(result: Pizza | Error) { 18 | if(result instanceof Error) { 19 | result.message 20 | } 21 | } 22 | 23 | function isPizza(maybePizza: any): maybePizza is Pizza { 24 | // You do the work to check if something is of a type 25 | if(maybePizza.toppings) return true; 26 | return false; 27 | } 28 | 29 | let someRandomThing: any; 30 | 31 | if(isPizza(someRandomThing)) { 32 | someRandomThing; // 33 | } 34 | 35 | 36 | export {} 37 | -------------------------------------------------------------------------------- /playground-finished/types-from-data.ts: -------------------------------------------------------------------------------- 1 | const countries = ['CA', 'US', 'FR', 'SR', 'NL'] as const; 2 | type Country = typeof countries[number]; 3 | 4 | function getFlagEmoji(countryCode: Country) { 5 | const codePoints = countryCode 6 | .toUpperCase() 7 | .split('') 8 | .map((char) => 127397 + char.charCodeAt(0)); 9 | return String.fromCodePoint(...codePoints); 10 | } 11 | 12 | getFlagEmoji('CA'); 13 | getFlagEmoji('NL'); 14 | 15 | const weights = { 16 | heavy: 900, 17 | light: 200, 18 | } as const; 19 | 20 | type Weights = typeof weights; 21 | // ^? 22 | type WeightKey = keyof Weights; 23 | // ^? 24 | type WeightValue = typeof weights[WeightKey]; 25 | // ^? 26 | 27 | function styleText(weight: WeightKey | WeightValue) { 28 | console.log('weight'); 29 | } 30 | 31 | styleText('heavy'); 32 | styleText('light'); 33 | styleText(900); 34 | styleText(weights.heavy); 35 | 36 | const vehicles = [ 37 | { name: 'Van', wheels: 4 }, 38 | { name: 'Car', wheels: 4 }, 39 | { name: 'Motorcycle', wheels: 2 }, 40 | ] as const; 41 | 42 | type Vehicle = { 43 | [Prop in keyof typeof vehicles[number]]: typeof vehicles[number][Prop]; 44 | }; 45 | 46 | export {}; 47 | -------------------------------------------------------------------------------- /playground-finished/unions.ts: -------------------------------------------------------------------------------- 1 | type Status = 'picked-up' | 'shipped' | 'delivered'; 2 | 3 | type Product = { 4 | status: Status; 5 | id: string; 6 | }; 7 | 8 | const myProduct: Product = { 9 | id: 'abc123', 10 | status: 'shipped', 11 | }; 12 | 13 | myProduct.status = 'delivered'; 14 | 15 | type MediaElement = HTMLVideoElement | HTMLAudioElement; 16 | 17 | const button = document.createElement('button'); 18 | function toggleMedia(element: MediaElement): void { 19 | if (element.paused) { 20 | element.play(); 21 | button.textContent = 'Pause'; 22 | } else { 23 | element.pause(); 24 | button.textContent = 'Play'; 25 | } 26 | } 27 | 28 | export {}; 29 | -------------------------------------------------------------------------------- /playground-finished/utility-types-START.ts: -------------------------------------------------------------------------------- 1 | type CreateProductInput = { 2 | name: string; 3 | price: number; 4 | description: string; 5 | tags: string[]; 6 | }; 7 | 8 | type Product = CreateProductInput & { 9 | created: Date; 10 | updated: Date; 11 | }; 12 | 13 | interface ProductCategory { 14 | name: string; 15 | description: string; 16 | subcategories: ProductCategory[]; 17 | } 18 | 19 | type TopLevelCategories = 'sports' | 'clothing' | 'outdoor' | 'kitchen'; 20 | type VariantBase = 'Mens' | 'Womens' | 'Kids'; 21 | type VariantSize = 'small' | 'medium' | 'large' | 'XL'; 22 | // Required 23 | 24 | // Partial 25 | // Pick 26 | // Omit 27 | // Exclude 28 | // Non nullable 29 | // ReadOnly 30 | // Lowercase and Capitalize 31 | // Record<> 32 | 33 | export {}; 34 | -------------------------------------------------------------------------------- /playground-finished??/satisfies.ts: -------------------------------------------------------------------------------- 1 | type Score = Record; 2 | 3 | const scores = { 4 | wes: 100, 5 | scott: 200, 6 | } satisfies Score; 7 | 8 | scores.wes = 200; 9 | scores.was = 200; 10 | scores.scott = 300; 11 | scores['200'] = 300; 12 | scores.scott.toFixed() 13 | 14 | type Setting = string | number | { [key: string]: Setting } | Setting[]; 15 | type Settings = Record; 16 | 17 | function getData() { 18 | return { you: 'good' }; 19 | } 20 | 21 | const mySettings = { 22 | title: `Wes' Website`, 23 | data: getData(), 24 | size: 200, 25 | overrides: [{ 'font-size': '20px' }], 26 | styleConfig: { 27 | color: 'red', 28 | }, 29 | } satisfies Settings; 30 | 31 | 32 | // Valid Properties 33 | mySettings.title = 'New title'; 34 | console.log(mySettings.size); 35 | // Invalid Properties 36 | console.log(mySettings.scott); 37 | mySettings.limt = 200; 38 | 39 | mySettings.size.toLocaleString(); 40 | 41 | mySettings.overrides.at(0)?.['font-size']; 42 | 43 | console.log(metaData.title); 44 | console.log(metaData.doesntExist); 45 | 46 | export { }; 47 | -------------------------------------------------------------------------------- /playground-finished??/teest.js: -------------------------------------------------------------------------------- 1 | const age = 200; 2 | -------------------------------------------------------------------------------- /playground-finished??/test.ts: -------------------------------------------------------------------------------- 1 | const name = 'John'; 2 | 3 | const age = parseInt('200'); 4 | const formEl = document.querySelector('form#add'); 5 | 6 | console.log(age, formEl); 7 | export {}; 8 | 9 | // any 10 | -------------------------------------------------------------------------------- /playground/satisfies.ts: -------------------------------------------------------------------------------- 1 | type Setting = string | number | { [key: string]: Setting } | Setting[]; 2 | type Settings = Record; 3 | 4 | function getData() { 5 | return { you: 'good' }; 6 | } 7 | 8 | const mySettings = { 9 | title: `Wes' Website`, 10 | data: getData(), 11 | size: 200, 12 | overrides: [{ 'font-size': '20px' }], 13 | styleConfig: { 14 | color: 'red', 15 | }, 16 | } satisfies Settings; 17 | 18 | 19 | // Valid Properties 20 | mySettings.title = 'New title'; 21 | console.log(mySettings.size); 22 | // Invalid Properties 23 | console.log(mySettings.scott); 24 | mySettings.limt = 200; 25 | 26 | mySettings.size.toLocaleString(); 27 | 28 | mySettings.overrides.at(0)?.['font-size']; 29 | 30 | console.log(metaData.title); 31 | console.log(metaData.doesntExist); 32 | 33 | export {}; 34 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## Tasty TypeScript! 2 | 3 | The starter files for the Tasty TypeScript course. 4 | 5 | `npm i && npm start` 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "./**/*" 4 | ], 5 | 6 | "compilerOptions": { 7 | "strict": true, 8 | "lib": [ 9 | "ESNext", 10 | "DOM", 11 | "DOM.Iterable" 12 | ], 13 | "skipLibCheck": true, 14 | "esModuleInterop": true, 15 | "module": "nodenext", 16 | "target": "esnext" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import { defineConfig, PluginOption } from 'vite'; 3 | // import { directoryPlugin } from 'vite-plugin-list-directory-contents'; 4 | import { directoryPlugin } from 'vite-plugin-list-directory-contents'; 5 | 6 | export default defineConfig({ 7 | // A simple vite config. We are using 1 plugin here so we can visually navigate the directory structure. Funny things here - Vite is whining about the plugin not having the correct params, because their types are missing something. So here we cast it with `as`. 8 | plugins: [directoryPlugin({ baseDir: __dirname }) as PluginOption], 9 | server: { 10 | port: 7777, 11 | host: true, 12 | }, 13 | }); 14 | --------------------------------------------------------------------------------