├── .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 |
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 |
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 |
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 |
49 |
--------------------------------------------------------------------------------
/exercises/focus-machine-start/icons/Airport.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
43 |
--------------------------------------------------------------------------------
/exercises/focus-machine-start/icons/Car.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
56 |
--------------------------------------------------------------------------------
/exercises/focus-machine-start/icons/Construction.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
50 |
--------------------------------------------------------------------------------
/exercises/focus-machine-start/icons/Fire.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
43 |
--------------------------------------------------------------------------------
/exercises/focus-machine-start/icons/Football.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
55 |
--------------------------------------------------------------------------------
/exercises/focus-machine-start/icons/Highway.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
46 |
--------------------------------------------------------------------------------
/exercises/focus-machine-start/icons/Laundry.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
52 |
--------------------------------------------------------------------------------
/exercises/focus-machine-start/icons/Restaurant.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
48 |
--------------------------------------------------------------------------------
/exercises/focus-machine-start/icons/coaster.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
47 |
--------------------------------------------------------------------------------
/exercises/focus-machine-start/icons/coffee.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
45 |
--------------------------------------------------------------------------------
/exercises/focus-machine-start/icons/doors.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
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 |
53 |
--------------------------------------------------------------------------------
/exercises/focus-machine-start/icons/mall.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
45 |
--------------------------------------------------------------------------------
/exercises/focus-machine-start/icons/speaker.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
51 |
--------------------------------------------------------------------------------
/exercises/focus-machine-start/icons/subway.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 |
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 |
49 |
--------------------------------------------------------------------------------
/exercises/focus-machine/icons/Airport.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
43 |
--------------------------------------------------------------------------------
/exercises/focus-machine/icons/Car.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
56 |
--------------------------------------------------------------------------------
/exercises/focus-machine/icons/Construction.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
50 |
--------------------------------------------------------------------------------
/exercises/focus-machine/icons/Fire.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
43 |
--------------------------------------------------------------------------------
/exercises/focus-machine/icons/Football.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
55 |
--------------------------------------------------------------------------------
/exercises/focus-machine/icons/Highway.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
46 |
--------------------------------------------------------------------------------
/exercises/focus-machine/icons/Laundry.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
52 |
--------------------------------------------------------------------------------
/exercises/focus-machine/icons/Restaurant.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
48 |
--------------------------------------------------------------------------------
/exercises/focus-machine/icons/coaster.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
47 |
--------------------------------------------------------------------------------
/exercises/focus-machine/icons/coffee.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
45 |
--------------------------------------------------------------------------------
/exercises/focus-machine/icons/doors.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
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 |
53 |
--------------------------------------------------------------------------------
/exercises/focus-machine/icons/mall.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
45 |
--------------------------------------------------------------------------------
/exercises/focus-machine/icons/speaker.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
51 |
--------------------------------------------------------------------------------
/exercises/focus-machine/icons/subway.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 |
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 |

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 |
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 |
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 |
20 |
32 |
33 |
34 |
35 |
40 |
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 |
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 |
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 |
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 |
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 |

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 |
49 |
--------------------------------------------------------------------------------
/finished-exercises/focus-machine/icons/Airport.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
43 |
--------------------------------------------------------------------------------
/finished-exercises/focus-machine/icons/Car.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
56 |
--------------------------------------------------------------------------------
/finished-exercises/focus-machine/icons/Construction.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
50 |
--------------------------------------------------------------------------------
/finished-exercises/focus-machine/icons/Fire.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
43 |
--------------------------------------------------------------------------------
/finished-exercises/focus-machine/icons/Football.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
55 |
--------------------------------------------------------------------------------
/finished-exercises/focus-machine/icons/Highway.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
46 |
--------------------------------------------------------------------------------
/finished-exercises/focus-machine/icons/Laundry.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
52 |
--------------------------------------------------------------------------------
/finished-exercises/focus-machine/icons/Restaurant.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
48 |
--------------------------------------------------------------------------------
/finished-exercises/focus-machine/icons/coaster.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
47 |
--------------------------------------------------------------------------------
/finished-exercises/focus-machine/icons/coffee.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
45 |
--------------------------------------------------------------------------------
/finished-exercises/focus-machine/icons/doors.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
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 |
53 |
--------------------------------------------------------------------------------
/finished-exercises/focus-machine/icons/mall.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
45 |
--------------------------------------------------------------------------------
/finished-exercises/focus-machine/icons/speaker.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
51 |
--------------------------------------------------------------------------------
/finished-exercises/focus-machine/icons/subway.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 |
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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------