make tairu work better with noscript
							
								
								
									
										261
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							
							
						
						| 
						 | 
				
			
			@ -168,6 +168,12 @@ dependencies = [
 | 
			
		|||
 "rustc-demangle",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "bit_field"
 | 
			
		||||
version = "0.10.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "bitflags"
 | 
			
		||||
version = "1.3.2"
 | 
			
		||||
| 
						 | 
				
			
			@ -189,6 +195,18 @@ dependencies = [
 | 
			
		|||
 "generic-array",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "bytemuck"
 | 
			
		||||
version = "1.14.3"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "byteorder"
 | 
			
		||||
version = "1.5.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "bytes"
 | 
			
		||||
version = "1.5.0"
 | 
			
		||||
| 
						 | 
				
			
			@ -261,6 +279,12 @@ dependencies = [
 | 
			
		|||
 "unicode-width",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "color_quant"
 | 
			
		||||
version = "1.1.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "colorchoice"
 | 
			
		||||
version = "1.0.0"
 | 
			
		||||
| 
						 | 
				
			
			@ -285,6 +309,46 @@ dependencies = [
 | 
			
		|||
 "libc",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "crc32fast"
 | 
			
		||||
version = "1.4.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "cfg-if",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "crossbeam-deque"
 | 
			
		||||
version = "0.8.5"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "crossbeam-epoch",
 | 
			
		||||
 "crossbeam-utils",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "crossbeam-epoch"
 | 
			
		||||
version = "0.9.18"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "crossbeam-utils",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "crossbeam-utils"
 | 
			
		||||
version = "0.8.19"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "crunchy"
 | 
			
		||||
version = "0.2.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "crypto-common"
 | 
			
		||||
version = "0.1.6"
 | 
			
		||||
| 
						 | 
				
			
			@ -305,6 +369,12 @@ dependencies = [
 | 
			
		|||
 "crypto-common",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "either"
 | 
			
		||||
version = "1.10.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "env_logger"
 | 
			
		||||
version = "0.10.0"
 | 
			
		||||
| 
						 | 
				
			
			@ -345,6 +415,50 @@ dependencies = [
 | 
			
		|||
 "libc",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "exr"
 | 
			
		||||
version = "1.72.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "887d93f60543e9a9362ef8a21beedd0a833c5d9610e18c67abe15a5963dcb1a4"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "bit_field",
 | 
			
		||||
 "flume",
 | 
			
		||||
 "half",
 | 
			
		||||
 "lebe",
 | 
			
		||||
 "miniz_oxide",
 | 
			
		||||
 "rayon-core",
 | 
			
		||||
 "smallvec",
 | 
			
		||||
 "zune-inflate",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "fdeflate"
 | 
			
		||||
version = "0.3.4"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "simd-adler32",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "flate2"
 | 
			
		||||
version = "1.0.28"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "crc32fast",
 | 
			
		||||
 "miniz_oxide",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "flume"
 | 
			
		||||
version = "0.11.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "spin",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "fnv"
 | 
			
		||||
version = "1.0.7"
 | 
			
		||||
| 
						 | 
				
			
			@ -420,6 +534,16 @@ dependencies = [
 | 
			
		|||
 "wasi",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "gif"
 | 
			
		||||
version = "0.12.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "color_quant",
 | 
			
		||||
 "weezl",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "gimli"
 | 
			
		||||
version = "0.27.3"
 | 
			
		||||
| 
						 | 
				
			
			@ -445,6 +569,16 @@ dependencies = [
 | 
			
		|||
 "tracing",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "half"
 | 
			
		||||
version = "2.3.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "bc52e53916c08643f1b56ec082790d1e86a32e58dc5268f897f313fbae7b4872"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "cfg-if",
 | 
			
		||||
 "crunchy",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "handlebars"
 | 
			
		||||
version = "4.3.7"
 | 
			
		||||
| 
						 | 
				
			
			@ -570,6 +704,24 @@ dependencies = [
 | 
			
		|||
 "tokio",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "image"
 | 
			
		||||
version = "0.24.8"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "034bbe799d1909622a74d1193aa50147769440040ff36cb2baa947609b0a4e23"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "bytemuck",
 | 
			
		||||
 "byteorder",
 | 
			
		||||
 "color_quant",
 | 
			
		||||
 "exr",
 | 
			
		||||
 "gif",
 | 
			
		||||
 "jpeg-decoder",
 | 
			
		||||
 "num-traits",
 | 
			
		||||
 "png",
 | 
			
		||||
 "qoi",
 | 
			
		||||
 "tiff",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "indexmap"
 | 
			
		||||
version = "2.0.0"
 | 
			
		||||
| 
						 | 
				
			
			@ -597,6 +749,21 @@ version = "1.0.9"
 | 
			
		|||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "jpeg-decoder"
 | 
			
		||||
version = "0.3.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "rayon",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "lebe"
 | 
			
		||||
version = "0.5.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "libc"
 | 
			
		||||
version = "0.2.153"
 | 
			
		||||
| 
						 | 
				
			
			@ -660,6 +827,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		|||
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "adler",
 | 
			
		||||
 "simd-adler32",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
| 
						 | 
				
			
			@ -673,6 +841,15 @@ dependencies = [
 | 
			
		|||
 "windows-sys",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "num-traits"
 | 
			
		||||
version = "0.2.18"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "autocfg",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "num_cpus"
 | 
			
		||||
version = "1.16.0"
 | 
			
		||||
| 
						 | 
				
			
			@ -803,6 +980,19 @@ version = "0.1.0"
 | 
			
		|||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "png"
 | 
			
		||||
version = "0.17.13"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "bitflags 1.3.2",
 | 
			
		||||
 "crc32fast",
 | 
			
		||||
 "fdeflate",
 | 
			
		||||
 "flate2",
 | 
			
		||||
 "miniz_oxide",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "ppv-lite86"
 | 
			
		||||
version = "0.2.17"
 | 
			
		||||
| 
						 | 
				
			
			@ -829,6 +1019,15 @@ dependencies = [
 | 
			
		|||
 "unicase",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "qoi"
 | 
			
		||||
version = "0.4.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "bytemuck",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "quote"
 | 
			
		||||
version = "1.0.33"
 | 
			
		||||
| 
						 | 
				
			
			@ -868,6 +1067,26 @@ dependencies = [
 | 
			
		|||
 "getrandom",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "rayon"
 | 
			
		||||
version = "1.8.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "either",
 | 
			
		||||
 "rayon-core",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "rayon-core"
 | 
			
		||||
version = "1.12.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "crossbeam-deque",
 | 
			
		||||
 "crossbeam-utils",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "redox_syscall"
 | 
			
		||||
version = "0.3.5"
 | 
			
		||||
| 
						 | 
				
			
			@ -1034,6 +1253,12 @@ dependencies = [
 | 
			
		|||
 "libc",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "simd-adler32"
 | 
			
		||||
version = "0.3.7"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "slab"
 | 
			
		||||
version = "0.4.9"
 | 
			
		||||
| 
						 | 
				
			
			@ -1059,6 +1284,15 @@ dependencies = [
 | 
			
		|||
 "windows-sys",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "spin"
 | 
			
		||||
version = "0.9.8"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "lock_api",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "strsim"
 | 
			
		||||
version = "0.10.0"
 | 
			
		||||
| 
						 | 
				
			
			@ -1111,6 +1345,17 @@ dependencies = [
 | 
			
		|||
 "syn",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "tiff"
 | 
			
		||||
version = "0.9.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "flate2",
 | 
			
		||||
 "jpeg-decoder",
 | 
			
		||||
 "weezl",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "tokio"
 | 
			
		||||
version = "1.36.0"
 | 
			
		||||
| 
						 | 
				
			
			@ -1277,6 +1522,7 @@ dependencies = [
 | 
			
		|||
 "env_logger",
 | 
			
		||||
 "handlebars",
 | 
			
		||||
 "http-body",
 | 
			
		||||
 "image",
 | 
			
		||||
 "log",
 | 
			
		||||
 "pulldown-cmark",
 | 
			
		||||
 "rand",
 | 
			
		||||
| 
						 | 
				
			
			@ -1368,6 +1614,12 @@ version = "0.11.0+wasi-snapshot-preview1"
 | 
			
		|||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "weezl"
 | 
			
		||||
version = "0.1.8"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "winapi"
 | 
			
		||||
version = "0.3.9"
 | 
			
		||||
| 
						 | 
				
			
			@ -1473,3 +1725,12 @@ checksum = "d09770118a7eb1ccaf4a594a221334119a44a814fcb0d31c5b85e83e97227a97"
 | 
			
		|||
dependencies = [
 | 
			
		||||
 "memchr",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "zune-inflate"
 | 
			
		||||
version = "0.2.54"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "simd-adler32",
 | 
			
		||||
]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,6 +29,13 @@ styles = ["post/tairu.css"]
 | 
			
		|||
    % id = "01HQ162WWAMCPC5M88QAXHX4BT"
 | 
			
		||||
    - so to help us learn, I made a little tile editor so that we can experiment with rendering tiles! have a look:
 | 
			
		||||
 | 
			
		||||
    <noscript>(…though you will need to enable JavaScript to try it out.
 | 
			
		||||
    seriously, pinky promise I won't ever track you!
 | 
			
		||||
    inspect the source code if you wanna.
 | 
			
		||||
    if not, you will have to deal with static pictures.
 | 
			
		||||
    but just keep in mind this was supposed to be an <strong><em>interactive</em></strong> exploration of autotiling techniques.
 | 
			
		||||
    cheers!)</noscript>
 | 
			
		||||
 | 
			
		||||
    ```javascript tairu
 | 
			
		||||
    import { Tilemap } from "tairu/tilemap.js";
 | 
			
		||||
    import { TileEditor } from "tairu/editor.js";
 | 
			
		||||
| 
						 | 
				
			
			@ -47,7 +54,7 @@ styles = ["post/tairu.css"]
 | 
			
		|||
    });
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
    ```output tairu
 | 
			
		||||
    ```output tairu 01HQ47ZX7520PJNPJ75M793R5G
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
        % id = "01HQ162WWAC3FN565QE3JAB87D"
 | 
			
		||||
| 
						 | 
				
			
			@ -58,6 +65,8 @@ styles = ["post/tairu.css"]
 | 
			
		|||
        console.log(tilemapSquare.at(3, 1));
 | 
			
		||||
        ```
 | 
			
		||||
        ```output tairu
 | 
			
		||||
        0
 | 
			
		||||
        1
 | 
			
		||||
        ```
 | 
			
		||||
 | 
			
		||||
        [`Uint8Array`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array
 | 
			
		||||
| 
						 | 
				
			
			@ -189,7 +198,7 @@ styles = ["post/tairu.css"]
 | 
			
		|||
        borderWidth: 4,
 | 
			
		||||
    });
 | 
			
		||||
    ```
 | 
			
		||||
    ```output tairu
 | 
			
		||||
    ```output tairu 01HQ49TJZFMK719KSE16SG3F7B
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
    % id = "01HQ162WWAAEKW1ECV5G3ZEY47"
 | 
			
		||||
| 
						 | 
				
			
			@ -407,7 +416,7 @@ styles = ["post/tairu.css"]
 | 
			
		|||
        tilesets: [heavyMetalTileset],
 | 
			
		||||
    });
 | 
			
		||||
    ```
 | 
			
		||||
    ```output tairu
 | 
			
		||||
    ```output tairu 01HQ49X8Z57FNMN3E79FYF8CMG
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
    % id = "01HQ162WWA03JAGJYCT0DRZP24"
 | 
			
		||||
| 
						 | 
				
			
			@ -429,7 +438,7 @@ styles = ["post/tairu.css"]
 | 
			
		|||
        tilesets: [heavyMetalTileset],
 | 
			
		||||
    });
 | 
			
		||||
    ```
 | 
			
		||||
    ```output tairu
 | 
			
		||||
    ```output tairu 01HQ49YDPQXYSAT5N6P241DG3C
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
    % id = "01HQ162WWAB0AYSPGB4AEVT03Z"
 | 
			
		||||
| 
						 | 
				
			
			@ -455,7 +464,7 @@ styles = ["post/tairu.css"]
 | 
			
		|||
        tilesets: [heavyMetalTileset],
 | 
			
		||||
    });
 | 
			
		||||
    ```
 | 
			
		||||
    ```output tairu
 | 
			
		||||
    ```output tairu 01HQ49Z8JWR75D85DGHCB34K8E
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
    % id = "01HQ1K39AS4VDW7DVTAGQ03WFM"
 | 
			
		||||
| 
						 | 
				
			
			@ -550,7 +559,7 @@ styles = ["post/tairu.css"]
 | 
			
		|||
            tilesets: [heavyMetalTileset],
 | 
			
		||||
        });
 | 
			
		||||
        ```
 | 
			
		||||
        ```output tairu
 | 
			
		||||
        ```output tairu 01HQ4A01MPE6JT5ZZFEN9S635W
 | 
			
		||||
        ```
 | 
			
		||||
 | 
			
		||||
        % id = "01HQ1K39AS7CRBZ67N1VVHCVME"
 | 
			
		||||
| 
						 | 
				
			
			@ -879,7 +888,7 @@ styles = ["post/tairu.css"]
 | 
			
		|||
        tilesets: [heavyMetalTileset47],
 | 
			
		||||
    });
 | 
			
		||||
    ```
 | 
			
		||||
    ```output tairu
 | 
			
		||||
    ```output tairu 01HQ4A11RRXEQ850598GFBJN0B
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
    % id = "01HQ1M84GSCXTPGVPXY840WCQ6"
 | 
			
		||||
| 
						 | 
				
			
			@ -907,7 +916,7 @@ new TilesetTileEditor({
 | 
			
		|||
    tilesets: [heavyMetalTileset47],
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
```output tairu
 | 
			
		||||
```output tairu 01HQ4A45WNAEJGCT2WDMQJHK14
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
:nap: <!--
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,9 +13,10 @@ clap = { version = "4.3.22", features = ["derive"] }
 | 
			
		|||
codespan-reporting = "0.11.1"
 | 
			
		||||
copy_dir = "0.1.3"
 | 
			
		||||
env_logger = "0.10.0"
 | 
			
		||||
log = { workspace = true }
 | 
			
		||||
handlebars = "4.3.7"
 | 
			
		||||
http-body = "1.0.0"
 | 
			
		||||
image = "0.24.8"
 | 
			
		||||
log = { workspace = true }
 | 
			
		||||
pulldown-cmark = { version = "0.9.3", default-features = false }
 | 
			
		||||
rand = "0.8.5"
 | 
			
		||||
serde = { version = "1.0.183", features = ["derive"] }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,7 +18,7 @@ use walkdir::WalkDir;
 | 
			
		|||
 | 
			
		||||
use crate::{
 | 
			
		||||
    cli::parse::parse_tree_with_diagnostics,
 | 
			
		||||
    config::Config,
 | 
			
		||||
    config::{Config, ConfigDerivedData},
 | 
			
		||||
    html::{
 | 
			
		||||
        breadcrumbs::breadcrumbs_to_html,
 | 
			
		||||
        navmap::{build_navigation_map, NavigationMap},
 | 
			
		||||
| 
						 | 
				
			
			@ -175,6 +175,7 @@ impl Generator {
 | 
			
		|||
        parsed_trees: impl IntoIterator<Item = ParsedTree>,
 | 
			
		||||
    ) -> anyhow::Result<()> {
 | 
			
		||||
        let mut handlebars = Handlebars::new();
 | 
			
		||||
        let mut config_derived_data = ConfigDerivedData::default();
 | 
			
		||||
 | 
			
		||||
        let mut template_file_ids = HashMap::new();
 | 
			
		||||
        for entry in WalkDir::new(paths.template_dir) {
 | 
			
		||||
| 
						 | 
				
			
			@ -232,6 +233,7 @@ impl Generator {
 | 
			
		|||
                &mut tree,
 | 
			
		||||
                treehouse,
 | 
			
		||||
                config,
 | 
			
		||||
                &mut config_derived_data,
 | 
			
		||||
                parsed_tree.file_id,
 | 
			
		||||
                &roots.branches,
 | 
			
		||||
            );
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
use std::{collections::HashMap, ffi::OsStr, path::Path};
 | 
			
		||||
use std::{collections::HashMap, ffi::OsStr, fs::File, io::BufReader, path::Path};
 | 
			
		||||
 | 
			
		||||
use anyhow::Context;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
| 
						 | 
				
			
			@ -108,3 +108,36 @@ impl Config {
 | 
			
		|||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Data derived from the config.
 | 
			
		||||
#[derive(Debug, Clone, Default)]
 | 
			
		||||
pub struct ConfigDerivedData {
 | 
			
		||||
    pub pic_sizes: HashMap<String, Option<PicSize>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Picture size. This is useful for emitting <img> elements with a specific size to eliminate layout shifting.
 | 
			
		||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
 | 
			
		||||
pub struct PicSize {
 | 
			
		||||
    pub width: u32,
 | 
			
		||||
    pub height: u32,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ConfigDerivedData {
 | 
			
		||||
    fn read_pic_size(config: &Config, pic_id: &str) -> Option<PicSize> {
 | 
			
		||||
        let pic_filename = config.pics.get(pic_id)?;
 | 
			
		||||
        let (width, height) = image::io::Reader::new(BufReader::new(
 | 
			
		||||
            File::open(format!("static/pic/{pic_filename}")).ok()?,
 | 
			
		||||
        ))
 | 
			
		||||
        .into_dimensions()
 | 
			
		||||
        .ok()?;
 | 
			
		||||
        Some(PicSize { width, height })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn pic_size(&mut self, config: &Config, pic_id: &str) -> Option<PicSize> {
 | 
			
		||||
        if !self.pic_sizes.contains_key(pic_id) {
 | 
			
		||||
            self.pic_sizes
 | 
			
		||||
                .insert(pic_id.to_owned(), Self::read_pic_size(config, pic_id));
 | 
			
		||||
        }
 | 
			
		||||
        self.pic_sizes.get(pic_id).copied().flatten()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,7 +30,7 @@ use pulldown_cmark::escape::{escape_href, escape_html, StrWrite};
 | 
			
		|||
use pulldown_cmark::{Alignment, CodeBlockKind, Event, LinkType, Tag};
 | 
			
		||||
use pulldown_cmark::{CowStr, Event::*};
 | 
			
		||||
 | 
			
		||||
use crate::config::Config;
 | 
			
		||||
use crate::config::{Config, ConfigDerivedData, PicSize};
 | 
			
		||||
use crate::state::Treehouse;
 | 
			
		||||
 | 
			
		||||
enum TableState {
 | 
			
		||||
| 
						 | 
				
			
			@ -41,6 +41,7 @@ enum TableState {
 | 
			
		|||
struct HtmlWriter<'a, I, W> {
 | 
			
		||||
    treehouse: &'a Treehouse,
 | 
			
		||||
    config: &'a Config,
 | 
			
		||||
    config_derived_data: &'a mut ConfigDerivedData,
 | 
			
		||||
    page_id: &'a str,
 | 
			
		||||
 | 
			
		||||
    /// Iterator supplying events.
 | 
			
		||||
| 
						 | 
				
			
			@ -68,6 +69,7 @@ where
 | 
			
		|||
    fn new(
 | 
			
		||||
        treehouse: &'a Treehouse,
 | 
			
		||||
        config: &'a Config,
 | 
			
		||||
        config_derived_data: &'a mut ConfigDerivedData,
 | 
			
		||||
        page_id: &'a str,
 | 
			
		||||
        iter: I,
 | 
			
		||||
        writer: W,
 | 
			
		||||
| 
						 | 
				
			
			@ -75,7 +77,9 @@ where
 | 
			
		|||
        Self {
 | 
			
		||||
            treehouse,
 | 
			
		||||
            config,
 | 
			
		||||
            config_derived_data,
 | 
			
		||||
            page_id,
 | 
			
		||||
 | 
			
		||||
            iter,
 | 
			
		||||
            writer,
 | 
			
		||||
            end_newline: true,
 | 
			
		||||
| 
						 | 
				
			
			@ -247,11 +251,11 @@ where
 | 
			
		|||
                            kind,
 | 
			
		||||
                            program_name,
 | 
			
		||||
                        } => {
 | 
			
		||||
                            self.write(match kind {
 | 
			
		||||
                            self.write(match &kind {
 | 
			
		||||
                                LiterateCodeKind::Input => {
 | 
			
		||||
                                    "<th-literate-program data-mode=\"input\" "
 | 
			
		||||
                                }
 | 
			
		||||
                                LiterateCodeKind::Output => {
 | 
			
		||||
                                LiterateCodeKind::Output { .. } => {
 | 
			
		||||
                                    "<th-literate-program data-mode=\"output\" "
 | 
			
		||||
                                }
 | 
			
		||||
                            })?;
 | 
			
		||||
| 
						 | 
				
			
			@ -261,7 +265,30 @@ where
 | 
			
		|||
                            escape_html(&mut self.writer, program_name)?;
 | 
			
		||||
                            self.write("\" data-language=\"")?;
 | 
			
		||||
                            escape_html(&mut self.writer, language)?;
 | 
			
		||||
                            self.write("\" role=\"code\">")
 | 
			
		||||
                            self.write("\" role=\"code\">")?;
 | 
			
		||||
 | 
			
		||||
                            if let LiterateCodeKind::Output { placeholder_pic_id } = kind {
 | 
			
		||||
                                if !placeholder_pic_id.is_empty() {
 | 
			
		||||
                                    self.write(
 | 
			
		||||
                                        "<img class=\"placeholder\" loading=\"lazy\" src=\"",
 | 
			
		||||
                                    )?;
 | 
			
		||||
                                    escape_html(
 | 
			
		||||
                                        &mut self.writer,
 | 
			
		||||
                                        &self.config.pic_url(placeholder_pic_id),
 | 
			
		||||
                                    )?;
 | 
			
		||||
                                    self.write("\"")?;
 | 
			
		||||
                                    if let Some(PicSize { width, height }) = self
 | 
			
		||||
                                        .config_derived_data
 | 
			
		||||
                                        .pic_size(self.config, placeholder_pic_id)
 | 
			
		||||
                                    {
 | 
			
		||||
                                        self.write(&format!(
 | 
			
		||||
                                            " width=\"{width}\" height=\"{height}\""
 | 
			
		||||
                                        ))?;
 | 
			
		||||
                                    }
 | 
			
		||||
                                    self.write(">")?;
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            Ok(())
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    CodeBlockKind::Indented => self.write("<pre><code>"),
 | 
			
		||||
| 
						 | 
				
			
			@ -556,9 +583,9 @@ where
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
 | 
			
		||||
enum LiterateCodeKind {
 | 
			
		||||
enum LiterateCodeKind<'a> {
 | 
			
		||||
    Input,
 | 
			
		||||
    Output,
 | 
			
		||||
    Output { placeholder_pic_id: &'a str },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum CodeBlockMode<'a> {
 | 
			
		||||
| 
						 | 
				
			
			@ -568,7 +595,7 @@ enum CodeBlockMode<'a> {
 | 
			
		|||
    },
 | 
			
		||||
    LiterateProgram {
 | 
			
		||||
        language: &'a str,
 | 
			
		||||
        kind: LiterateCodeKind,
 | 
			
		||||
        kind: LiterateCodeKind<'a>,
 | 
			
		||||
        program_name: &'a str,
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -578,14 +605,16 @@ impl<'a> CodeBlockMode<'a> {
 | 
			
		|||
        if language.is_empty() {
 | 
			
		||||
            CodeBlockMode::PlainText
 | 
			
		||||
        } else if let Some((language, program_name)) = language.split_once(' ') {
 | 
			
		||||
            let (program_name, placeholder_pic_id) =
 | 
			
		||||
                program_name.split_once(' ').unwrap_or((program_name, ""));
 | 
			
		||||
            CodeBlockMode::LiterateProgram {
 | 
			
		||||
                language,
 | 
			
		||||
                kind: if language == "output" {
 | 
			
		||||
                    LiterateCodeKind::Output
 | 
			
		||||
                    LiterateCodeKind::Output { placeholder_pic_id }
 | 
			
		||||
                } else {
 | 
			
		||||
                    LiterateCodeKind::Input
 | 
			
		||||
                },
 | 
			
		||||
                program_name,
 | 
			
		||||
                program_name: program_name.split(' ').next().unwrap(),
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            CodeBlockMode::SyntaxHighlightOnly { language }
 | 
			
		||||
| 
						 | 
				
			
			@ -624,12 +653,13 @@ pub fn push_html<'a, I>(
 | 
			
		|||
    s: &mut String,
 | 
			
		||||
    treehouse: &'a Treehouse,
 | 
			
		||||
    config: &'a Config,
 | 
			
		||||
    config_derived_data: &'a mut ConfigDerivedData,
 | 
			
		||||
    page_id: &'a str,
 | 
			
		||||
    iter: I,
 | 
			
		||||
) where
 | 
			
		||||
    I: Iterator<Item = Event<'a>>,
 | 
			
		||||
{
 | 
			
		||||
    HtmlWriter::new(treehouse, config, page_id, iter, s)
 | 
			
		||||
    HtmlWriter::new(treehouse, config, config_derived_data, page_id, iter, s)
 | 
			
		||||
        .run()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,7 @@ use pulldown_cmark::{BrokenLink, LinkType};
 | 
			
		|||
use treehouse_format::pull::BranchKind;
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    config::Config,
 | 
			
		||||
    config::{Config, ConfigDerivedData},
 | 
			
		||||
    html::EscapeAttribute,
 | 
			
		||||
    state::{FileId, Treehouse},
 | 
			
		||||
    tree::{
 | 
			
		||||
| 
						 | 
				
			
			@ -19,6 +19,7 @@ pub fn branch_to_html(
 | 
			
		|||
    s: &mut String,
 | 
			
		||||
    treehouse: &mut Treehouse,
 | 
			
		||||
    config: &Config,
 | 
			
		||||
    config_derived_data: &mut ConfigDerivedData,
 | 
			
		||||
    file_id: FileId,
 | 
			
		||||
    branch_id: SemaBranchId,
 | 
			
		||||
) {
 | 
			
		||||
| 
						 | 
				
			
			@ -145,6 +146,7 @@ pub fn branch_to_html(
 | 
			
		|||
            s,
 | 
			
		||||
            treehouse,
 | 
			
		||||
            config,
 | 
			
		||||
            config_derived_data,
 | 
			
		||||
            treehouse.tree_path(file_id).expect(".tree file expected"),
 | 
			
		||||
            markdown_parser,
 | 
			
		||||
        );
 | 
			
		||||
| 
						 | 
				
			
			@ -197,7 +199,7 @@ pub fn branch_to_html(
 | 
			
		|||
                let num_children = branch.children.len();
 | 
			
		||||
                for i in 0..num_children {
 | 
			
		||||
                    let child_id = treehouse.tree.branch(branch_id).children[i];
 | 
			
		||||
                    branch_to_html(s, treehouse, config, file_id, child_id);
 | 
			
		||||
                    branch_to_html(s, treehouse, config, config_derived_data, file_id, child_id);
 | 
			
		||||
                }
 | 
			
		||||
                s.push_str("</ul>");
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -213,12 +215,13 @@ pub fn branches_to_html(
 | 
			
		|||
    s: &mut String,
 | 
			
		||||
    treehouse: &mut Treehouse,
 | 
			
		||||
    config: &Config,
 | 
			
		||||
    config_derived_data: &mut ConfigDerivedData,
 | 
			
		||||
    file_id: FileId,
 | 
			
		||||
    branches: &[SemaBranchId],
 | 
			
		||||
) {
 | 
			
		||||
    s.push_str("<ul>");
 | 
			
		||||
    for &child in branches {
 | 
			
		||||
        branch_to_html(s, treehouse, config, file_id, child);
 | 
			
		||||
        branch_to_html(s, treehouse, config, config_derived_data, file_id, child);
 | 
			
		||||
    }
 | 
			
		||||
    s.push_str("</ul>");
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -556,10 +556,21 @@ th-literate-program[data-mode="output"] {
 | 
			
		|||
    border: none;
 | 
			
		||||
    border-radius: 0;
 | 
			
		||||
 | 
			
		||||
    & iframe {
 | 
			
		||||
    & iframe,
 | 
			
		||||
    & img.placeholder {
 | 
			
		||||
        border-style: none;
 | 
			
		||||
        border-radius: 4px;
 | 
			
		||||
        display: block;
 | 
			
		||||
        transition: opacity var(--transition-duration);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    & iframe,
 | 
			
		||||
    & img.placeholder.loading {
 | 
			
		||||
        opacity: 50%;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    & iframe.loaded {
 | 
			
		||||
        opacity: 100%;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* The inner iframe is hidden until something requests display. */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,16 +19,19 @@ function getLiterateProgram(name) {
 | 
			
		|||
    return literatePrograms.get(name);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getLiterateProgramWorkerCommands(name) {
 | 
			
		||||
function getLiterateProgramWorkerCommands(name, count) {
 | 
			
		||||
    let commands = [];
 | 
			
		||||
    let literateProgram = getLiterateProgram(name);
 | 
			
		||||
    for (let frame of literateProgram.frames) {
 | 
			
		||||
 | 
			
		||||
    for (let i = 0; i < count; ++i) {
 | 
			
		||||
        let frame = literateProgram.frames[i];
 | 
			
		||||
        if (frame.mode == "input") {
 | 
			
		||||
            commands.push({ kind: "module", source: frame.textContent });
 | 
			
		||||
        } else if (frame.mode == "output") {
 | 
			
		||||
            commands.push({ kind: "output" });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return commands;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -180,6 +183,8 @@ class OutputMode {
 | 
			
		|||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.frame.placeholderImage.classList.add("loading");
 | 
			
		||||
 | 
			
		||||
        this.frame.program.onChanged.push(_ => this.evaluate());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -187,7 +192,7 @@ class OutputMode {
 | 
			
		|||
        this.requestConsoleClear();
 | 
			
		||||
        this.iframe.contentWindow.postMessage({
 | 
			
		||||
            action: "eval",
 | 
			
		||||
            input: getLiterateProgramWorkerCommands(this.frame.programName),
 | 
			
		||||
            input: getLiterateProgramWorkerCommands(this.frame.programName, this.frame.frameIndex + 1),
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -229,6 +234,15 @@ class OutputMode {
 | 
			
		|||
        // iframe cannot be `display: none` to get its scrollWidth/scrollHeight.
 | 
			
		||||
        this.iframe.classList.remove("hidden");
 | 
			
		||||
 | 
			
		||||
        if (this.frame.placeholderImage != null) {
 | 
			
		||||
            // Fade the iframe in after it becomes visible, and remove the image.
 | 
			
		||||
            setTimeout(() => this.iframe.classList.add("loaded"), 0);
 | 
			
		||||
            this.frame.removeChild(this.frame.placeholderImage);
 | 
			
		||||
        } else {
 | 
			
		||||
            // If there is no image, don't do the fade in.
 | 
			
		||||
            this.iframe.classList.add("loaded");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let width = this.iframe.contentDocument.body.scrollWidth;
 | 
			
		||||
        let height = this.iframe.contentDocument.body.scrollHeight;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -244,8 +258,11 @@ class OutputMode {
 | 
			
		|||
class LiterateProgram extends HTMLElement {
 | 
			
		||||
    connectedCallback() {
 | 
			
		||||
        this.programName = this.getAttribute("data-program");
 | 
			
		||||
        this.frameIndex = this.program.frames.length;
 | 
			
		||||
        this.program.frames.push(this);
 | 
			
		||||
 | 
			
		||||
        this.placeholderImage = this.getElementsByClassName("placeholder")[0];
 | 
			
		||||
 | 
			
		||||
        this.mode = this.getAttribute("data-mode");
 | 
			
		||||
        if (this.mode == "input") {
 | 
			
		||||
            this.modeImpl = new InputMode(this);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,87 +0,0 @@
 | 
			
		|||
// A frameworking class assigning some CSS classes to the canvas to make it integrate nicer with CSS.
 | 
			
		||||
export class Frame extends HTMLCanvasElement {
 | 
			
		||||
    static fontFace = "RecVar";
 | 
			
		||||
    static monoFontFace = "RecVarMono";
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async connectedCallback() {
 | 
			
		||||
        this.style.cssText = `
 | 
			
		||||
            margin-top: 8px;
 | 
			
		||||
            margin-bottom: 4px;
 | 
			
		||||
            border-radius: 4px;
 | 
			
		||||
            max-width: 100%;
 | 
			
		||||
        `;
 | 
			
		||||
 | 
			
		||||
        this.ctx = this.getContext("2d");
 | 
			
		||||
 | 
			
		||||
        requestAnimationFrame(this.#drawLoop.bind(this));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #drawLoop() {
 | 
			
		||||
        this.ctx.font = "14px RecVar";
 | 
			
		||||
        this.draw();
 | 
			
		||||
        requestAnimationFrame(this.#drawLoop.bind(this));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Override this!
 | 
			
		||||
    draw() {
 | 
			
		||||
        throw new ReferenceError("draw() must be overridden");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getTextPositionInBox(text, x, y, width, height, hAlign, vAlign) {
 | 
			
		||||
        let measurements = this.ctx.measureText(text);
 | 
			
		||||
 | 
			
		||||
        let leftX;
 | 
			
		||||
        switch (hAlign) {
 | 
			
		||||
            case "left":
 | 
			
		||||
                leftX = x;
 | 
			
		||||
                break;
 | 
			
		||||
            case "center":
 | 
			
		||||
                leftX = x + width / 2 - measurements.width / 2;
 | 
			
		||||
                break;
 | 
			
		||||
            case "right":
 | 
			
		||||
                leftX = x + width - measurements.width;
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let textHeight = measurements.fontBoundingBoxAscent;
 | 
			
		||||
        let baselineY;
 | 
			
		||||
        switch (vAlign) {
 | 
			
		||||
            case "top":
 | 
			
		||||
                baselineY = y + textHeight;
 | 
			
		||||
                break;
 | 
			
		||||
            case "center":
 | 
			
		||||
                baselineY = y + height / 2 + textHeight / 2;
 | 
			
		||||
                break;
 | 
			
		||||
            case "bottom":
 | 
			
		||||
                baselineY = y + height;
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return { leftX, baselineY };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get scaleInViewportX() {
 | 
			
		||||
        return this.clientWidth / this.width;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get scaleInViewportY() {
 | 
			
		||||
        return this.clientHeight / this.height;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getMousePositionFromEvent(event) {
 | 
			
		||||
        return {
 | 
			
		||||
            x: event.offsetX / this.scaleInViewportX,
 | 
			
		||||
            y: event.offsetY / this.scaleInViewportY,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function defineFrame(elementName, claß) { // because `class` is a keyword.
 | 
			
		||||
    customElements.define(elementName, claß, { extends: "canvas" });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
defineFrame("tairu--frame", Frame);
 | 
			
		||||
| 
						 | 
				
			
			@ -1,76 +0,0 @@
 | 
			
		|||
import { TileEditor } from 'tairu/editor.js';
 | 
			
		||||
 | 
			
		||||
export function alignTextInRectangle(ctx, text, x, y, width, height, hAlign, vAlign) {
 | 
			
		||||
    let measurements = ctx.measureText(text);
 | 
			
		||||
 | 
			
		||||
    let leftX;
 | 
			
		||||
    switch (hAlign) {
 | 
			
		||||
        case "left":
 | 
			
		||||
            leftX = x;
 | 
			
		||||
            break;
 | 
			
		||||
        case "center":
 | 
			
		||||
            leftX = x + width / 2 - measurements.width / 2;
 | 
			
		||||
            break;
 | 
			
		||||
        case "right":
 | 
			
		||||
            leftX = x + width - measurements.width;
 | 
			
		||||
            break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let textHeight = measurements.fontBoundingBoxAscent;
 | 
			
		||||
    let baselineY;
 | 
			
		||||
    switch (vAlign) {
 | 
			
		||||
        case "top":
 | 
			
		||||
            baselineY = y + textHeight;
 | 
			
		||||
            break;
 | 
			
		||||
        case "center":
 | 
			
		||||
            baselineY = y + height / 2 + textHeight / 2;
 | 
			
		||||
            break;
 | 
			
		||||
        case "bottom":
 | 
			
		||||
            baselineY = y + height;
 | 
			
		||||
            break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return { leftX, baselineY };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function shouldConnect(a, b) {
 | 
			
		||||
    return a == b;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class TileEditorWithCardinalDirections extends TileEditor {
 | 
			
		||||
    constructor(options) {
 | 
			
		||||
        super(options);
 | 
			
		||||
        this.colorScheme.tiles[1] = "#f96565";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    drawConnectionText(text, enabled, tileX, tileY, hAlign, vAlign) {
 | 
			
		||||
        this.ctx.beginPath();
 | 
			
		||||
        this.ctx.fillStyle = enabled ? "#6c023e" : "#d84161";
 | 
			
		||||
        this.ctx.font = `800 14px ${Frame.monoFontFace}`;
 | 
			
		||||
        const padding = 2;
 | 
			
		||||
        let topLeftX = tileX * this.tileSize + padding;
 | 
			
		||||
        let topLeftY = tileY * this.tileSize + padding;
 | 
			
		||||
        let rectSize = this.tileSize - padding * 2;
 | 
			
		||||
        let { leftX, baselineY } = this.getTextPositionInBox(text, topLeftX, topLeftY, rectSize, rectSize, hAlign, vAlign);
 | 
			
		||||
        this.ctx.fillText(text, leftX, baselineY);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    drawTiles() {
 | 
			
		||||
        super.drawTiles();
 | 
			
		||||
        for (let y = 0; y < this.tilemap.height; ++y) {
 | 
			
		||||
            for (let x = 0; x < this.tilemap.width; ++x) {
 | 
			
		||||
                let tile = this.tilemap.at(x, y);
 | 
			
		||||
                if (canConnect(tile)) {
 | 
			
		||||
                    let connectedWithEast = shouldConnect(tile, this.tilemap.at(x + 1, y));
 | 
			
		||||
                    let connectedWithSouth = shouldConnect(tile, this.tilemap.at(x, y + 1));
 | 
			
		||||
                    let connectedWithNorth = shouldConnect(tile, this.tilemap.at(x, y - 1));
 | 
			
		||||
                    let connectedWithWest = shouldConnect(tile, this.tilemap.at(x - 1, y));
 | 
			
		||||
                    this.drawConnectionText("E", connectedWithEast, x, y, "right", "center");
 | 
			
		||||
                    this.drawConnectionText("S", connectedWithSouth, x, y, "center", "bottom");
 | 
			
		||||
                    this.drawConnectionText("N", connectedWithNorth, x, y, "center", "top");
 | 
			
		||||
                    this.drawConnectionText("W", connectedWithWest, x, y, "left", "center");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,47 +0,0 @@
 | 
			
		|||
import { Tilemap } from './tilemap.js';
 | 
			
		||||
 | 
			
		||||
const alphabet = " x";
 | 
			
		||||
 | 
			
		||||
function parseTilemap(lineArray) {
 | 
			
		||||
    let tilemap = new Tilemap(lineArray[0].length, lineArray.length);
 | 
			
		||||
    for (let y in lineArray) {
 | 
			
		||||
        let line = lineArray[y];
 | 
			
		||||
        for (let x = 0; x < line.length; ++x) {
 | 
			
		||||
            let char = line.charAt(x);
 | 
			
		||||
            tilemap.setAt(x, y, alphabet.indexOf(char));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return tilemap;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    bitwiseAutotiling: parseTilemap([
 | 
			
		||||
        "         ",
 | 
			
		||||
        "   xxx   ",
 | 
			
		||||
        "   xxx   ",
 | 
			
		||||
        "   xxx   ",
 | 
			
		||||
        "         ",
 | 
			
		||||
    ]),
 | 
			
		||||
    bitwiseAutotilingChapter2: parseTilemap([
 | 
			
		||||
        "         ",
 | 
			
		||||
        "   x     ",
 | 
			
		||||
        "   x     ",
 | 
			
		||||
        "   xxx   ",
 | 
			
		||||
        "         ",
 | 
			
		||||
    ]),
 | 
			
		||||
    bitwiseAutotilingCorners: parseTilemap([
 | 
			
		||||
        "     ",
 | 
			
		||||
        " x x ",
 | 
			
		||||
        "  x  ",
 | 
			
		||||
        " x x ",
 | 
			
		||||
        "     ",
 | 
			
		||||
    ]),
 | 
			
		||||
    bitwiseAutotiling47: parseTilemap([
 | 
			
		||||
        "      x  ",
 | 
			
		||||
        "   x     ",
 | 
			
		||||
        "  xx xx  ",
 | 
			
		||||
        "   xxxx  ",
 | 
			
		||||
        "  x      ",
 | 
			
		||||
    ]),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -7,3 +7,12 @@ document.addEventListener("click", event => {
 | 
			
		|||
        event.preventDefault();
 | 
			
		||||
    }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
// Certain words don't make sense if scripts are disabled.
 | 
			
		||||
class YesScript extends HTMLElement {
 | 
			
		||||
    connectedCallback() {
 | 
			
		||||
        this.classList.add("yes-indeed");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
customElements.define("th-yesscript", YesScript);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										
											BIN
										
									
								
								static/pic/01HQ47ZX7520PJNPJ75M793R5G-noscript-placeholder.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.6 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								static/pic/01HQ49TJZFMK719KSE16SG3F7B-noscript-placeholder.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.7 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								static/pic/01HQ49X8Z57FNMN3E79FYF8CMG-noscript-placeholder.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 4.1 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								static/pic/01HQ49YDPQXYSAT5N6P241DG3C-noscript-placeholder.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 4.1 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								static/pic/01HQ49Z8JWR75D85DGHCB34K8E-noscript-placeholder.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.6 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								static/pic/01HQ4A01MPE6JT5ZZFEN9S635W-noscript-placeholder.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 2.5 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								static/pic/01HQ4A11RRXEQ850598GFBJN0B-noscript-placeholder.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 4.6 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								static/pic/01HQ4A45WNAEJGCT2WDMQJHK14-noscript-placeholder.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 30 KiB  |