Integrate templates project
This commit is contained in:
		
							parent
							
								
									3a5d4b9756
								
							
						
					
					
						commit
						76f5246814
					
				
					 55 changed files with 11946 additions and 144 deletions
				
			
		| 
						 | 
					@ -1,4 +1,4 @@
 | 
				
			||||||
((nil . ((indent-tabs-mode . nil)
 | 
					((rust-mode . ((indent-tabs-mode . nil)
 | 
				
			||||||
         (tab-width . 4)
 | 
					               (tab-width . 4)
 | 
				
			||||||
         (fill-column . 80)
 | 
					               (fill-column . 80)
 | 
				
			||||||
         (projectile-project-run-cmd . "cargo run -- --dev-mode --template-directory ~/Documents/Projects/tlaternet-templates/result"))))
 | 
					               (projectile-project-run-cmd . "cd server && cargo run -- --dev-mode --template-directory ~/Documents/Projects/tlaternet-templates/result"))))
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										1
									
								
								.envrc
									
										
									
									
									
								
							
							
						
						
									
										1
									
								
								.envrc
									
										
									
									
									
								
							| 
						 | 
					@ -1 +0,0 @@
 | 
				
			||||||
use flake
 | 
					 | 
				
			||||||
							
								
								
									
										4
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							| 
						 | 
					@ -1,2 +1,2 @@
 | 
				
			||||||
/target
 | 
					/server/target
 | 
				
			||||||
/templates/
 | 
					/result
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										314
									
								
								flake.lock
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										314
									
								
								flake.lock
									
										
									
										generated
									
									
									
								
							| 
						 | 
					@ -1,56 +1,208 @@
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
  "nodes": {
 | 
					  "nodes": {
 | 
				
			||||||
    "flake-utils": {
 | 
					    "alejandra": {
 | 
				
			||||||
      "locked": {
 | 
					 | 
				
			||||||
        "lastModified": 1656928814,
 | 
					 | 
				
			||||||
        "narHash": "sha256-RIFfgBuKz6Hp89yRr7+NR5tzIAbn52h8vT6vXkYjZoM=",
 | 
					 | 
				
			||||||
        "owner": "numtide",
 | 
					 | 
				
			||||||
        "repo": "flake-utils",
 | 
					 | 
				
			||||||
        "rev": "7e2a3b3dfd9af950a856d66b0a7d01e3c18aa249",
 | 
					 | 
				
			||||||
        "type": "github"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "original": {
 | 
					 | 
				
			||||||
        "owner": "numtide",
 | 
					 | 
				
			||||||
        "repo": "flake-utils",
 | 
					 | 
				
			||||||
        "type": "github"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "naersk": {
 | 
					 | 
				
			||||||
      "inputs": {
 | 
					      "inputs": {
 | 
				
			||||||
 | 
					        "fenix": "fenix",
 | 
				
			||||||
 | 
					        "flakeCompat": "flakeCompat",
 | 
				
			||||||
        "nixpkgs": [
 | 
					        "nixpkgs": [
 | 
				
			||||||
 | 
					          "dream2nix",
 | 
				
			||||||
          "nixpkgs"
 | 
					          "nixpkgs"
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "locked": {
 | 
					      "locked": {
 | 
				
			||||||
        "lastModified": 1662220400,
 | 
					        "lastModified": 1658427149,
 | 
				
			||||||
        "narHash": "sha256-9o2OGQqu4xyLZP9K6kNe1pTHnyPz0Wr3raGYnr9AIgY=",
 | 
					        "narHash": "sha256-ToD/1z/q5VHsLMrS2h96vjJoLho59eNRtknOUd19ey8=",
 | 
				
			||||||
        "owner": "nmattia",
 | 
					        "owner": "kamadorueda",
 | 
				
			||||||
        "repo": "naersk",
 | 
					        "repo": "alejandra",
 | 
				
			||||||
        "rev": "6944160c19cb591eb85bbf9b2f2768a935623ed3",
 | 
					        "rev": "f5a22afd2adfb249b4e68e0b33aa1f0fb73fb1be",
 | 
				
			||||||
        "type": "github"
 | 
					        "type": "github"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "original": {
 | 
					      "original": {
 | 
				
			||||||
        "owner": "nmattia",
 | 
					        "owner": "kamadorueda",
 | 
				
			||||||
        "repo": "naersk",
 | 
					        "repo": "alejandra",
 | 
				
			||||||
        "type": "github"
 | 
					        "type": "github"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "nix-filter": {
 | 
					    "crane": {
 | 
				
			||||||
 | 
					      "flake": false,
 | 
				
			||||||
      "locked": {
 | 
					      "locked": {
 | 
				
			||||||
        "lastModified": 1661201956,
 | 
					        "lastModified": 1661875961,
 | 
				
			||||||
        "narHash": "sha256-RizGJH/buaw9A2+fiBf9WnXYw4LZABB5kMAZIEE5/T8=",
 | 
					        "narHash": "sha256-f1h/2c6Teeu1ofAHWzrS8TwBPcnN+EEu+z1sRVmMQTk=",
 | 
				
			||||||
 | 
					        "owner": "ipetkov",
 | 
				
			||||||
 | 
					        "repo": "crane",
 | 
				
			||||||
 | 
					        "rev": "d9f394e4e20e97c2a60c3ad82c2b6ef99be19e24",
 | 
				
			||||||
 | 
					        "type": "github"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "original": {
 | 
				
			||||||
 | 
					        "owner": "ipetkov",
 | 
				
			||||||
 | 
					        "repo": "crane",
 | 
				
			||||||
 | 
					        "type": "github"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "devshell": {
 | 
				
			||||||
 | 
					      "flake": false,
 | 
				
			||||||
 | 
					      "locked": {
 | 
				
			||||||
 | 
					        "lastModified": 1653917170,
 | 
				
			||||||
 | 
					        "narHash": "sha256-FyxOnEE/V4PNEcMU62ikY4FfYPo349MOhMM97HS0XEo=",
 | 
				
			||||||
        "owner": "numtide",
 | 
					        "owner": "numtide",
 | 
				
			||||||
        "repo": "nix-filter",
 | 
					        "repo": "devshell",
 | 
				
			||||||
        "rev": "3b821578685d661a10b563cba30b1861eec05748",
 | 
					        "rev": "fc7a3e3adde9bbcab68af6d1e3c6eb738e296a92",
 | 
				
			||||||
        "type": "github"
 | 
					        "type": "github"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "original": {
 | 
					      "original": {
 | 
				
			||||||
        "owner": "numtide",
 | 
					        "owner": "numtide",
 | 
				
			||||||
        "repo": "nix-filter",
 | 
					        "repo": "devshell",
 | 
				
			||||||
        "type": "github"
 | 
					        "type": "github"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "dream2nix": {
 | 
				
			||||||
 | 
					      "inputs": {
 | 
				
			||||||
 | 
					        "alejandra": "alejandra",
 | 
				
			||||||
 | 
					        "crane": "crane",
 | 
				
			||||||
 | 
					        "devshell": "devshell",
 | 
				
			||||||
 | 
					        "flake-utils-pre-commit": "flake-utils-pre-commit",
 | 
				
			||||||
 | 
					        "gomod2nix": "gomod2nix",
 | 
				
			||||||
 | 
					        "mach-nix": "mach-nix",
 | 
				
			||||||
 | 
					        "nixpkgs": "nixpkgs",
 | 
				
			||||||
 | 
					        "poetry2nix": "poetry2nix",
 | 
				
			||||||
 | 
					        "pre-commit-hooks": "pre-commit-hooks"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "locked": {
 | 
				
			||||||
 | 
					        "lastModified": 1663323895,
 | 
				
			||||||
 | 
					        "narHash": "sha256-ZmI9C8HNVz2w3OnB79WR/LIgVEY8tDnR8tEPi3hMiJk=",
 | 
				
			||||||
 | 
					        "owner": "nix-community",
 | 
				
			||||||
 | 
					        "repo": "dream2nix",
 | 
				
			||||||
 | 
					        "rev": "25be741ec92c77b8308ca6a7ab89593fe37b6542",
 | 
				
			||||||
 | 
					        "type": "github"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "original": {
 | 
				
			||||||
 | 
					        "owner": "nix-community",
 | 
				
			||||||
 | 
					        "repo": "dream2nix",
 | 
				
			||||||
 | 
					        "type": "github"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "fenix": {
 | 
				
			||||||
 | 
					      "inputs": {
 | 
				
			||||||
 | 
					        "nixpkgs": [
 | 
				
			||||||
 | 
					          "dream2nix",
 | 
				
			||||||
 | 
					          "alejandra",
 | 
				
			||||||
 | 
					          "nixpkgs"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "rust-analyzer-src": "rust-analyzer-src"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "locked": {
 | 
				
			||||||
 | 
					        "lastModified": 1657607339,
 | 
				
			||||||
 | 
					        "narHash": "sha256-HaqoAwlbVVZH2n4P3jN2FFPMpVuhxDy1poNOR7kzODc=",
 | 
				
			||||||
 | 
					        "owner": "nix-community",
 | 
				
			||||||
 | 
					        "repo": "fenix",
 | 
				
			||||||
 | 
					        "rev": "b814c83d9e6aa5a28d0cf356ecfdafb2505ad37d",
 | 
				
			||||||
 | 
					        "type": "github"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "original": {
 | 
				
			||||||
 | 
					        "owner": "nix-community",
 | 
				
			||||||
 | 
					        "repo": "fenix",
 | 
				
			||||||
 | 
					        "type": "github"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "fenix_2": {
 | 
				
			||||||
 | 
					      "inputs": {
 | 
				
			||||||
 | 
					        "nixpkgs": [
 | 
				
			||||||
 | 
					          "nixpkgs"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "rust-analyzer-src": "rust-analyzer-src_2"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "locked": {
 | 
				
			||||||
 | 
					        "lastModified": 1663396212,
 | 
				
			||||||
 | 
					        "narHash": "sha256-dlK10QPTDYNpJ/vl2QPKOTrqEbQwAR/v2f4+xsetTkw=",
 | 
				
			||||||
 | 
					        "owner": "nix-community",
 | 
				
			||||||
 | 
					        "repo": "fenix",
 | 
				
			||||||
 | 
					        "rev": "263cd7f991c07a9592a6e825bfc37b23b00eb244",
 | 
				
			||||||
 | 
					        "type": "github"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "original": {
 | 
				
			||||||
 | 
					        "owner": "nix-community",
 | 
				
			||||||
 | 
					        "repo": "fenix",
 | 
				
			||||||
 | 
					        "type": "github"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "flake-utils-pre-commit": {
 | 
				
			||||||
 | 
					      "locked": {
 | 
				
			||||||
 | 
					        "lastModified": 1644229661,
 | 
				
			||||||
 | 
					        "narHash": "sha256-1YdnJAsNy69bpcjuoKdOYQX0YxZBiCYZo4Twxerqv7k=",
 | 
				
			||||||
 | 
					        "owner": "numtide",
 | 
				
			||||||
 | 
					        "repo": "flake-utils",
 | 
				
			||||||
 | 
					        "rev": "3cecb5b042f7f209c56ffd8371b2711a290ec797",
 | 
				
			||||||
 | 
					        "type": "github"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "original": {
 | 
				
			||||||
 | 
					        "owner": "numtide",
 | 
				
			||||||
 | 
					        "repo": "flake-utils",
 | 
				
			||||||
 | 
					        "type": "github"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "flakeCompat": {
 | 
				
			||||||
 | 
					      "flake": false,
 | 
				
			||||||
 | 
					      "locked": {
 | 
				
			||||||
 | 
					        "lastModified": 1650374568,
 | 
				
			||||||
 | 
					        "narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=",
 | 
				
			||||||
 | 
					        "owner": "edolstra",
 | 
				
			||||||
 | 
					        "repo": "flake-compat",
 | 
				
			||||||
 | 
					        "rev": "b4a34015c698c7793d592d66adbab377907a2be8",
 | 
				
			||||||
 | 
					        "type": "github"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "original": {
 | 
				
			||||||
 | 
					        "owner": "edolstra",
 | 
				
			||||||
 | 
					        "repo": "flake-compat",
 | 
				
			||||||
 | 
					        "type": "github"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "gomod2nix": {
 | 
				
			||||||
 | 
					      "flake": false,
 | 
				
			||||||
 | 
					      "locked": {
 | 
				
			||||||
 | 
					        "lastModified": 1627572165,
 | 
				
			||||||
 | 
					        "narHash": "sha256-MFpwnkvQpauj799b4QTBJQFEddbD02+Ln5k92QyHOSk=",
 | 
				
			||||||
 | 
					        "owner": "tweag",
 | 
				
			||||||
 | 
					        "repo": "gomod2nix",
 | 
				
			||||||
 | 
					        "rev": "67f22dd738d092c6ba88e420350ada0ed4992ae8",
 | 
				
			||||||
 | 
					        "type": "github"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "original": {
 | 
				
			||||||
 | 
					        "owner": "tweag",
 | 
				
			||||||
 | 
					        "repo": "gomod2nix",
 | 
				
			||||||
 | 
					        "type": "github"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "mach-nix": {
 | 
				
			||||||
 | 
					      "flake": false,
 | 
				
			||||||
 | 
					      "locked": {
 | 
				
			||||||
 | 
					        "lastModified": 1634711045,
 | 
				
			||||||
 | 
					        "narHash": "sha256-m5A2Ty88NChLyFhXucECj6+AuiMZPHXNbw+9Kcs7F6Y=",
 | 
				
			||||||
 | 
					        "owner": "DavHau",
 | 
				
			||||||
 | 
					        "repo": "mach-nix",
 | 
				
			||||||
 | 
					        "rev": "4433f74a97b94b596fa6cd9b9c0402104aceef5d",
 | 
				
			||||||
 | 
					        "type": "github"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "original": {
 | 
				
			||||||
 | 
					        "id": "mach-nix",
 | 
				
			||||||
 | 
					        "type": "indirect"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "nixpkgs": {
 | 
					    "nixpkgs": {
 | 
				
			||||||
 | 
					      "locked": {
 | 
				
			||||||
 | 
					        "lastModified": 1657638268,
 | 
				
			||||||
 | 
					        "narHash": "sha256-blBNtQSslAFkg0Gym9fWNJk+bPxGSZib4SOcPrmTPi4=",
 | 
				
			||||||
 | 
					        "owner": "NixOS",
 | 
				
			||||||
 | 
					        "repo": "nixpkgs",
 | 
				
			||||||
 | 
					        "rev": "d80993b5f885515254746ba6d1917276ee386149",
 | 
				
			||||||
 | 
					        "type": "github"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "original": {
 | 
				
			||||||
 | 
					        "id": "nixpkgs",
 | 
				
			||||||
 | 
					        "ref": "nixos-unstable",
 | 
				
			||||||
 | 
					        "type": "indirect"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "nixpkgs_2": {
 | 
				
			||||||
      "locked": {
 | 
					      "locked": {
 | 
				
			||||||
        "lastModified": 1663244735,
 | 
					        "lastModified": 1663244735,
 | 
				
			||||||
        "narHash": "sha256-+EukKkeAx6ithOLM1u5x4D12ZFuoi6vpPYjhNDmLz1o=",
 | 
					        "narHash": "sha256-+EukKkeAx6ithOLM1u5x4D12ZFuoi6vpPYjhNDmLz1o=",
 | 
				
			||||||
| 
						 | 
					@ -66,85 +218,87 @@
 | 
				
			||||||
        "type": "github"
 | 
					        "type": "github"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "nixpkgs_2": {
 | 
					    "poetry2nix": {
 | 
				
			||||||
 | 
					      "flake": false,
 | 
				
			||||||
      "locked": {
 | 
					      "locked": {
 | 
				
			||||||
        "lastModified": 1660318005,
 | 
					        "lastModified": 1632969109,
 | 
				
			||||||
        "narHash": "sha256-g9WCa9lVUmOV6dYRbEPjv/TLOR5hamjeCcKExVGS3OQ=",
 | 
					        "narHash": "sha256-jPDclkkiAy5m2gGLBlKgH+lQtbF7tL4XxBrbSzw+Ioc=",
 | 
				
			||||||
        "owner": "nixos",
 | 
					        "owner": "nix-community",
 | 
				
			||||||
        "repo": "nixpkgs",
 | 
					        "repo": "poetry2nix",
 | 
				
			||||||
        "rev": "5c211b47aeadcc178c5320afd4e74c7eed5c389f",
 | 
					        "rev": "aee8f04296c39d88155e05d25cfc59dfdd41cc77",
 | 
				
			||||||
        "type": "github"
 | 
					        "type": "github"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "original": {
 | 
					      "original": {
 | 
				
			||||||
        "owner": "nixos",
 | 
					        "owner": "nix-community",
 | 
				
			||||||
        "ref": "nixos-22.05",
 | 
					        "ref": "1.21.0",
 | 
				
			||||||
        "repo": "nixpkgs",
 | 
					        "repo": "poetry2nix",
 | 
				
			||||||
        "type": "github"
 | 
					        "type": "github"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "npmlock2nix": {
 | 
					    "pre-commit-hooks": {
 | 
				
			||||||
      "flake": false,
 | 
					      "inputs": {
 | 
				
			||||||
 | 
					        "flake-utils": [
 | 
				
			||||||
 | 
					          "dream2nix",
 | 
				
			||||||
 | 
					          "flake-utils-pre-commit"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "nixpkgs": [
 | 
				
			||||||
 | 
					          "dream2nix",
 | 
				
			||||||
 | 
					          "nixpkgs"
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
      "locked": {
 | 
					      "locked": {
 | 
				
			||||||
        "lastModified": 1654775747,
 | 
					        "lastModified": 1646153636,
 | 
				
			||||||
        "narHash": "sha256-9pXHDpIjmsK5390wmpGHu9aA4QOPpegPBvThHeBlef4=",
 | 
					        "narHash": "sha256-AlWHMzK+xJ1mG267FdT8dCq/HvLCA6jwmx2ZUy5O8tY=",
 | 
				
			||||||
        "owner": "nix-community",
 | 
					        "owner": "cachix",
 | 
				
			||||||
        "repo": "npmlock2nix",
 | 
					        "repo": "pre-commit-hooks.nix",
 | 
				
			||||||
        "rev": "5c4f247688fc91d665df65f71c81e0726621aaa8",
 | 
					        "rev": "b6bc0b21e1617e2b07d8205e7fae7224036dfa4b",
 | 
				
			||||||
        "type": "github"
 | 
					        "type": "github"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "original": {
 | 
					      "original": {
 | 
				
			||||||
        "owner": "nix-community",
 | 
					        "owner": "cachix",
 | 
				
			||||||
        "repo": "npmlock2nix",
 | 
					        "repo": "pre-commit-hooks.nix",
 | 
				
			||||||
        "type": "github"
 | 
					        "type": "github"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "root": {
 | 
					    "root": {
 | 
				
			||||||
      "inputs": {
 | 
					      "inputs": {
 | 
				
			||||||
        "naersk": "naersk",
 | 
					        "dream2nix": "dream2nix",
 | 
				
			||||||
        "nixpkgs": "nixpkgs",
 | 
					        "fenix": "fenix_2",
 | 
				
			||||||
        "rust-overlay": "rust-overlay",
 | 
					        "nixpkgs": "nixpkgs_2"
 | 
				
			||||||
        "tlaternet-templates": "tlaternet-templates"
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "rust-overlay": {
 | 
					    "rust-analyzer-src": {
 | 
				
			||||||
      "inputs": {
 | 
					      "flake": false,
 | 
				
			||||||
        "flake-utils": "flake-utils",
 | 
					 | 
				
			||||||
        "nixpkgs": [
 | 
					 | 
				
			||||||
          "nixpkgs"
 | 
					 | 
				
			||||||
        ]
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "locked": {
 | 
					      "locked": {
 | 
				
			||||||
        "lastModified": 1663297375,
 | 
					        "lastModified": 1657557289,
 | 
				
			||||||
        "narHash": "sha256-7pjd2x9fSXXynIzp9XiXjbYys7sR6MKCot/jfGL7dgE=",
 | 
					        "narHash": "sha256-PRW+nUwuqNTRAEa83SfX+7g+g8nQ+2MMbasQ9nt6+UM=",
 | 
				
			||||||
        "owner": "oxalica",
 | 
					        "owner": "rust-lang",
 | 
				
			||||||
        "repo": "rust-overlay",
 | 
					        "repo": "rust-analyzer",
 | 
				
			||||||
        "rev": "0678b6187a153eb0baa9688335b002fe14ba6712",
 | 
					        "rev": "caf23f29144b371035b864a1017dbc32573ad56d",
 | 
				
			||||||
        "type": "github"
 | 
					        "type": "github"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "original": {
 | 
					      "original": {
 | 
				
			||||||
        "owner": "oxalica",
 | 
					        "owner": "rust-lang",
 | 
				
			||||||
        "repo": "rust-overlay",
 | 
					        "ref": "nightly",
 | 
				
			||||||
 | 
					        "repo": "rust-analyzer",
 | 
				
			||||||
        "type": "github"
 | 
					        "type": "github"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "tlaternet-templates": {
 | 
					    "rust-analyzer-src_2": {
 | 
				
			||||||
      "inputs": {
 | 
					      "flake": false,
 | 
				
			||||||
        "nix-filter": "nix-filter",
 | 
					 | 
				
			||||||
        "nixpkgs": "nixpkgs_2",
 | 
					 | 
				
			||||||
        "npmlock2nix": "npmlock2nix"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "locked": {
 | 
					      "locked": {
 | 
				
			||||||
        "lastModified": 1663345814,
 | 
					        "lastModified": 1662896065,
 | 
				
			||||||
        "narHash": "sha256-wIl8P+Hv8zHwBATlEoppPNJMpcR2EiQ4dbkgGXszmf8=",
 | 
					        "narHash": "sha256-1LkSsXzI1JTAmP/GMTz4fTJd8y/tw8R79l96q+h7mu8=",
 | 
				
			||||||
        "ref": "master",
 | 
					        "owner": "rust-lang",
 | 
				
			||||||
        "rev": "789431c13cf1e906cbaf48e9b1078056c8ec3cc8",
 | 
					        "repo": "rust-analyzer",
 | 
				
			||||||
        "revCount": 111,
 | 
					        "rev": "2e9f1204ca01c3e20898d4a67c8b84899d394a88",
 | 
				
			||||||
        "type": "git",
 | 
					        "type": "github"
 | 
				
			||||||
        "url": "https://gitea.tlater.net/tlaternet/tlaternet-templates.git"
 | 
					 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "original": {
 | 
					      "original": {
 | 
				
			||||||
        "type": "git",
 | 
					        "owner": "rust-lang",
 | 
				
			||||||
        "url": "https://gitea.tlater.net/tlaternet/tlaternet-templates.git"
 | 
					        "ref": "nightly",
 | 
				
			||||||
 | 
					        "repo": "rust-analyzer",
 | 
				
			||||||
 | 
					        "type": "github"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										95
									
								
								flake.nix
									
										
									
									
									
								
							
							
						
						
									
										95
									
								
								flake.nix
									
										
									
									
									
								
							| 
						 | 
					@ -3,78 +3,63 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  inputs = {
 | 
					  inputs = {
 | 
				
			||||||
    nixpkgs.url = "github:nixos/nixpkgs/nixos-22.05";
 | 
					    nixpkgs.url = "github:nixos/nixpkgs/nixos-22.05";
 | 
				
			||||||
    rust-overlay = {
 | 
					    dream2nix.url = "github:nix-community/dream2nix";
 | 
				
			||||||
      url = "github:oxalica/rust-overlay";
 | 
					 | 
				
			||||||
      inputs.nixpkgs.follows = "nixpkgs";
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    naersk = {
 | 
					 | 
				
			||||||
      url = "github:nmattia/naersk";
 | 
					 | 
				
			||||||
      inputs.nixpkgs.follows = "nixpkgs";
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    tlaternet-templates = {
 | 
					    fenix = {
 | 
				
			||||||
      url = "git+https://gitea.tlater.net/tlaternet/tlaternet-templates.git";
 | 
					      url = "github:nix-community/fenix";
 | 
				
			||||||
      # No need to override anything here; we can save some downloads
 | 
					      inputs.nixpkgs.follows = "nixpkgs";
 | 
				
			||||||
      # if we rely on the webserver to do that.
 | 
					 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  outputs = {
 | 
					  outputs = {
 | 
				
			||||||
    self,
 | 
					    self,
 | 
				
			||||||
    nixpkgs,
 | 
					    nixpkgs,
 | 
				
			||||||
    rust-overlay,
 | 
					    dream2nix,
 | 
				
			||||||
    naersk,
 | 
					    fenix,
 | 
				
			||||||
    tlaternet-templates,
 | 
					 | 
				
			||||||
  }: let
 | 
					  }: let
 | 
				
			||||||
    # At the moment, we only deploy to x86_64-linux. Update when we
 | 
					    # At the moment, we only deploy to x86_64-linux. Update when we
 | 
				
			||||||
    # care about another platform.
 | 
					    # care about another platform.
 | 
				
			||||||
    system = "x86_64-linux";
 | 
					    system = "x86_64-linux";
 | 
				
			||||||
    overlays = [
 | 
					    flakeOutputs = import ./nix/packages.nix {inherit nixpkgs dream2nix fenix system;};
 | 
				
			||||||
      rust-overlay.overlays.default
 | 
					 | 
				
			||||||
    ];
 | 
					 | 
				
			||||||
    pkgs = import nixpkgs {inherit system overlays;};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # Rust build config
 | 
					 | 
				
			||||||
    rust-toolchain = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
 | 
					 | 
				
			||||||
    naersk-lib = naersk.lib.${system}.override {
 | 
					 | 
				
			||||||
      cargo = rust-toolchain;
 | 
					 | 
				
			||||||
      rustc = rust-toolchain;
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    buildInputs = with pkgs; [
 | 
					 | 
				
			||||||
      pkg-config
 | 
					 | 
				
			||||||
      openssl
 | 
					 | 
				
			||||||
    ];
 | 
					 | 
				
			||||||
  in {
 | 
					  in {
 | 
				
			||||||
    packages.${system} = rec {
 | 
					    packages.${system} = {
 | 
				
			||||||
      tlaternet-webserver = naersk-lib.buildPackage {
 | 
					      server = flakeOutputs.server.packages.${system}.default;
 | 
				
			||||||
        inherit buildInputs;
 | 
					      templates = flakeOutputs.templates.packages.${system}.default;
 | 
				
			||||||
        pname = "tlaternet-webserver";
 | 
					 | 
				
			||||||
        root = pkgs.lib.cleanSource self;
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
      default = tlaternet-webserver;
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    apps.${system} = {
 | 
					 | 
				
			||||||
      run-with-templates = let
 | 
					 | 
				
			||||||
        script = pkgs.writeShellScriptBin "run-with-templates" ''
 | 
					 | 
				
			||||||
          RUST_LOG=info ${self.packages.${system}.tlaternet-webserver}/bin/tlaternet-webserver \
 | 
					 | 
				
			||||||
              --template-directory ${tlaternet-templates.packages.${system}.default}
 | 
					 | 
				
			||||||
        '';
 | 
					 | 
				
			||||||
      in {
 | 
					 | 
				
			||||||
        type = "app";
 | 
					 | 
				
			||||||
        program = "${script}/bin/run-with-templates";
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    devShells.${system} = {
 | 
					    devShells.${system} = {
 | 
				
			||||||
      default = pkgs.mkShell {
 | 
					      templates = flakeOutputs.templates.devShells.${system}.default.overrideAttrs (old: {
 | 
				
			||||||
        packages = builtins.concatLists [
 | 
					        buildInputs = with nixpkgs.legacyPackages.${system};
 | 
				
			||||||
          buildInputs
 | 
					 | 
				
			||||||
          [
 | 
					          [
 | 
				
			||||||
            (rust-toolchain.override {
 | 
					            yj
 | 
				
			||||||
              extensions = ["rust-src" "rust-analysis" "rust-analyzer-preview"];
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
          ]
 | 
					          ]
 | 
				
			||||||
 | 
					          ++ old.buildInputs;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        shellHook =
 | 
				
			||||||
 | 
					          ''
 | 
				
			||||||
 | 
					            # Update package.json
 | 
				
			||||||
 | 
					            if [ -e ./package.json ]; then
 | 
				
			||||||
 | 
					                unlink ./package.json
 | 
				
			||||||
 | 
					            fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            cat ./package.yaml | yj > ./package.json
 | 
				
			||||||
 | 
					          ''
 | 
				
			||||||
 | 
					          + old.shellHook;
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      server = nixpkgs.legacyPackages.${system}.mkShell {
 | 
				
			||||||
 | 
					        packages = [
 | 
				
			||||||
 | 
					          (flakeOutputs.server.rust-toolchain.withComponents [
 | 
				
			||||||
 | 
					            "rustc"
 | 
				
			||||||
 | 
					            "cargo"
 | 
				
			||||||
 | 
					            "rustfmt"
 | 
				
			||||||
 | 
					            "rust-std"
 | 
				
			||||||
 | 
					            "rust-docs"
 | 
				
			||||||
 | 
					            "clippy"
 | 
				
			||||||
 | 
					            "rust-src"
 | 
				
			||||||
 | 
					            "rust-analysis"
 | 
				
			||||||
 | 
					          ])
 | 
				
			||||||
 | 
					          fenix.packages.${system}.rust-analyzer
 | 
				
			||||||
        ];
 | 
					        ];
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										94
									
								
								nix/packages.nix
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								nix/packages.nix
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,94 @@
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  nixpkgs,
 | 
				
			||||||
 | 
					  dream2nix,
 | 
				
			||||||
 | 
					  fenix,
 | 
				
			||||||
 | 
					  system,
 | 
				
			||||||
 | 
					}: {
 | 
				
			||||||
 | 
					  server = let
 | 
				
			||||||
 | 
					    rust-toolchain = fenix.packages.${system}.stable;
 | 
				
			||||||
 | 
					  in
 | 
				
			||||||
 | 
					    dream2nix.lib.makeFlakeOutputs {
 | 
				
			||||||
 | 
					      systems = [system];
 | 
				
			||||||
 | 
					      config.projectRoot = ../server;
 | 
				
			||||||
 | 
					      source = ../server;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      packageOverrides = {
 | 
				
			||||||
 | 
					        "^.*".set-toolchain.overrideRustToolchain = old: {
 | 
				
			||||||
 | 
					          cargo = rust-toolchain.minimalToolchain;
 | 
				
			||||||
 | 
					          rustc = rust-toolchain.minimalToolchain;
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    // {
 | 
				
			||||||
 | 
					      inherit rust-toolchain;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  templates = let
 | 
				
			||||||
 | 
					    inherit (nixpkgs.legacyPackages.${system}) runCommandLocal yj;
 | 
				
			||||||
 | 
					  in
 | 
				
			||||||
 | 
					    dream2nix.lib.makeFlakeOutputs {
 | 
				
			||||||
 | 
					      systems = [system];
 | 
				
			||||||
 | 
					      config.projectRoot = ../templates;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      # Generate `package.json` from `package.yaml`, since the nodejs
 | 
				
			||||||
 | 
					      # ecosystem doesn't support yaml.
 | 
				
			||||||
 | 
					      source = runCommandLocal "templates" {nativeBuildInputs = [yj];} ''
 | 
				
			||||||
 | 
					        cp -r ${../templates} $out/
 | 
				
			||||||
 | 
					        chmod u+w $out
 | 
				
			||||||
 | 
					        yj < ${../templates/package.yaml} > $out/package.json
 | 
				
			||||||
 | 
					      '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      packageOverrides = {
 | 
				
			||||||
 | 
					        tlaternet = {
 | 
				
			||||||
 | 
					          add-build-script = {
 | 
				
			||||||
 | 
					            # Dream2nix' built-in install script assumes this is just
 | 
				
			||||||
 | 
					            # a usual npm package and will install it in
 | 
				
			||||||
 | 
					            # `node_modules`.
 | 
				
			||||||
 | 
					            #
 | 
				
			||||||
 | 
					            # Parcel will detect this, and completely break all
 | 
				
			||||||
 | 
					            # configuration. Furthermore, we don't actually want to
 | 
				
			||||||
 | 
					            # install this as if it was an npm library.
 | 
				
			||||||
 | 
					            #
 | 
				
			||||||
 | 
					            # The easiest way to fix this is just to rename the
 | 
				
			||||||
 | 
					            # top-level directory.
 | 
				
			||||||
 | 
					            preBuild = ''
 | 
				
			||||||
 | 
					              # Rename top-level directory so parcel doesn't think we're in a
 | 
				
			||||||
 | 
					              # node_module
 | 
				
			||||||
 | 
					              mv ../../node_modules ../../top-level
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              # Rewrite $PATH and co. to use the new path.
 | 
				
			||||||
 | 
					              export PATH=''${PATH//lib\/node_modules\/.bin/lib\/top-level\/.bin}
 | 
				
			||||||
 | 
					              export NODE_PATH=''${NODE_PATH//lib\/node_modules/lib\/top-level}
 | 
				
			||||||
 | 
					            '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            installPhase = ''
 | 
				
			||||||
 | 
					              # For some reason, dream2nix builds in the out directory. Don't ask
 | 
				
			||||||
 | 
					              # me, I don't know either.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              # First, go to a sane directory and back up our actual build output
 | 
				
			||||||
 | 
					              mv dist /build/dist
 | 
				
			||||||
 | 
					              cd /build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              # Then, delete everything currently in $out
 | 
				
			||||||
 | 
					              chmod -R u+rwx $out
 | 
				
			||||||
 | 
					              rm -r $out
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              # Finally, actually install our output
 | 
				
			||||||
 | 
					              mv dist $out
 | 
				
			||||||
 | 
					            '';
 | 
				
			||||||
 | 
					          };
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        sharp = {
 | 
				
			||||||
 | 
					          add-libvips = {
 | 
				
			||||||
 | 
					            buildInputs = old:
 | 
				
			||||||
 | 
					              old
 | 
				
			||||||
 | 
					              ++ (with nixpkgs.legacyPackages.${system}; [
 | 
				
			||||||
 | 
					                vips
 | 
				
			||||||
 | 
					                pkg-config
 | 
				
			||||||
 | 
					              ]);
 | 
				
			||||||
 | 
					          };
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,2 +0,0 @@
 | 
				
			||||||
[toolchain]
 | 
					 | 
				
			||||||
channel = "nightly"
 | 
					 | 
				
			||||||
							
								
								
									
										0
									
								
								Cargo.lock → server/Cargo.lock
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										0
									
								
								Cargo.lock → server/Cargo.lock
									
										
									
										generated
									
									
									
								
							
							
								
								
									
										59
									
								
								templates/.eslintrc.yaml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								templates/.eslintrc.yaml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,59 @@
 | 
				
			||||||
 | 
					root: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					parser: "@typescript-eslint/parser"
 | 
				
			||||||
 | 
					parserOptions:
 | 
				
			||||||
 | 
					  project:
 | 
				
			||||||
 | 
					    - ./tsconfig.json
 | 
				
			||||||
 | 
					plugins:
 | 
				
			||||||
 | 
					  - "@typescript-eslint"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extends:
 | 
				
			||||||
 | 
					  - eslint:recommended
 | 
				
			||||||
 | 
					  - plugin:@typescript-eslint/recommended
 | 
				
			||||||
 | 
					  - plugin:@typescript-eslint/recommended-requiring-type-checking
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					env:
 | 
				
			||||||
 | 
					  es6: true
 | 
				
			||||||
 | 
					  browser: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# {
 | 
				
			||||||
 | 
					#     "parser": "@typescript-eslint/parser",
 | 
				
			||||||
 | 
					#     "plugins": [
 | 
				
			||||||
 | 
					#         "@typescript-eslint"
 | 
				
			||||||
 | 
					#     ],
 | 
				
			||||||
 | 
					#     "env": {
 | 
				
			||||||
 | 
					#         "es6": true,
 | 
				
			||||||
 | 
					#         "browser": true,
 | 
				
			||||||
 | 
					#         "jquery": true
 | 
				
			||||||
 | 
					#     },
 | 
				
			||||||
 | 
					#     "extends": [
 | 
				
			||||||
 | 
					#         "eslint:recommended",
 | 
				
			||||||
 | 
					#         "plugin:@typescript-eslint/recommended"
 | 
				
			||||||
 | 
					#     ],
 | 
				
			||||||
 | 
					#     "rules": {
 | 
				
			||||||
 | 
					#         "indent": [
 | 
				
			||||||
 | 
					#             "error",
 | 
				
			||||||
 | 
					#             4
 | 
				
			||||||
 | 
					#         ],
 | 
				
			||||||
 | 
					#         "linebreak-style": [
 | 
				
			||||||
 | 
					#             "error",
 | 
				
			||||||
 | 
					#             "unix"
 | 
				
			||||||
 | 
					#         ],
 | 
				
			||||||
 | 
					#         "quotes": [
 | 
				
			||||||
 | 
					#             "error",
 | 
				
			||||||
 | 
					#             "double"
 | 
				
			||||||
 | 
					#         ],
 | 
				
			||||||
 | 
					#         "semi": [
 | 
				
			||||||
 | 
					#             "warn",
 | 
				
			||||||
 | 
					#             "always"
 | 
				
			||||||
 | 
					#         ],
 | 
				
			||||||
 | 
					#         "no-console": [
 | 
				
			||||||
 | 
					#             "off"
 | 
				
			||||||
 | 
					#         ],
 | 
				
			||||||
 | 
					#         "no-unused-vars": [
 | 
				
			||||||
 | 
					#             "warn",
 | 
				
			||||||
 | 
					#             { "argsIgnorePattern": "^_" }
 | 
				
			||||||
 | 
					#         ]
 | 
				
			||||||
 | 
					#     }
 | 
				
			||||||
 | 
					# }
 | 
				
			||||||
							
								
								
									
										5
									
								
								templates/.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								templates/.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,5 @@
 | 
				
			||||||
 | 
					.parcel-cache/
 | 
				
			||||||
 | 
					dist/
 | 
				
			||||||
 | 
					node_modules/
 | 
				
			||||||
 | 
					package.json
 | 
				
			||||||
 | 
					result
 | 
				
			||||||
							
								
								
									
										6
									
								
								templates/.parcelrc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								templates/.parcelrc
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,6 @@
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "extends": ["@parcel/config-default"],
 | 
				
			||||||
 | 
					  "transformers": {
 | 
				
			||||||
 | 
					    "*.mp3": [ "@parcel/transformer-raw" ]
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										36
									
								
								templates/.posthtmlrc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								templates/.posthtmlrc
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,36 @@
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "plugins": {
 | 
				
			||||||
 | 
					    "posthtml-markdownit": {
 | 
				
			||||||
 | 
					      "root": "src",
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "posthtml-extend": {
 | 
				
			||||||
 | 
					      "root": "src",
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "posthtml-include": {
 | 
				
			||||||
 | 
					      "root": "src",
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "posthtml-favicons": {
 | 
				
			||||||
 | 
					      "root": "src",
 | 
				
			||||||
 | 
					      "outDir": "./dist/",
 | 
				
			||||||
 | 
					      "configuration": {
 | 
				
			||||||
 | 
					        "appName": "tlater.net",
 | 
				
			||||||
 | 
					        "appShortName": "tlater.net",
 | 
				
			||||||
 | 
					        "appDescription": "tlater's home page",
 | 
				
			||||||
 | 
					        "developerName": "Tristan Daniël Maat",
 | 
				
			||||||
 | 
					        "developerURL": "https://tlater.net",
 | 
				
			||||||
 | 
					        "dir": "auto",
 | 
				
			||||||
 | 
					        "lang": "en-US",
 | 
				
			||||||
 | 
					        "background": "#0f0f0f",
 | 
				
			||||||
 | 
					        "theme_color": "#99d1ce",
 | 
				
			||||||
 | 
					        "appleStatusBarStyle": "black-translucent",
 | 
				
			||||||
 | 
					        "display": "browser",
 | 
				
			||||||
 | 
					        "orientation": "any",
 | 
				
			||||||
 | 
					        "start_url": "https://tlater.net",
 | 
				
			||||||
 | 
					        "version": "1.0",
 | 
				
			||||||
 | 
					        "icons": {
 | 
				
			||||||
 | 
					          "favicons": true,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										11
									
								
								templates/.prettierrc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								templates/.prettierrc
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,11 @@
 | 
				
			||||||
 | 
					# -*- yaml -*-
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					tabWidth: 4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					overrides:
 | 
				
			||||||
 | 
					  - files: "**/*.html"
 | 
				
			||||||
 | 
					    options:
 | 
				
			||||||
 | 
					      tabWidth: 2
 | 
				
			||||||
 | 
					  - files: "**/*.scss"
 | 
				
			||||||
 | 
					    options:
 | 
				
			||||||
 | 
					      tabWidth: 2
 | 
				
			||||||
							
								
								
									
										9539
									
								
								templates/package-lock.json
									
										
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										9539
									
								
								templates/package-lock.json
									
										
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										85
									
								
								templates/package.yaml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								templates/package.yaml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,85 @@
 | 
				
			||||||
 | 
					name: tlaternet
 | 
				
			||||||
 | 
					version: 1.0.0
 | 
				
			||||||
 | 
					description: tlaternet web interface
 | 
				
			||||||
 | 
					author: Tristan Maat <tm@tlater.net>
 | 
				
			||||||
 | 
					license: MIT
 | 
				
			||||||
 | 
					private: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					dependencies:
 | 
				
			||||||
 | 
					  # Libraries
 | 
				
			||||||
 | 
					  gl-matrix: ^3.4.3   # To help with 3D math in WebGL code
 | 
				
			||||||
 | 
					  classnames: ^2.3.1  # To manage CSS class names in react code
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Fonts
 | 
				
			||||||
 | 
					  hack-font: ^3.3.0
 | 
				
			||||||
 | 
					  '@fontsource/arimo': ^4.5.8
 | 
				
			||||||
 | 
					  '@fontsource/nunito': ^4.5.9
 | 
				
			||||||
 | 
					  '@fortawesome/fontawesome-free': ^6.1.1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Frameworks for static content
 | 
				
			||||||
 | 
					  bulma: ^0.9.4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # React-redux stuff
 | 
				
			||||||
 | 
					  react: ^18.2.0
 | 
				
			||||||
 | 
					  react-dom: ^18.2.0
 | 
				
			||||||
 | 
					  react-use-error-boundary: ^3.0.0 # TODO(tlater): Remove when react implement their own
 | 
				
			||||||
 | 
					  redux: ^4.2.0
 | 
				
			||||||
 | 
					  '@reduxjs/toolkit': ^1.8.3
 | 
				
			||||||
 | 
					  react-redux: ^8.0.2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					devDependencies:
 | 
				
			||||||
 | 
					  # Parcel & plugins
 | 
				
			||||||
 | 
					  parcel: ^2.7.0
 | 
				
			||||||
 | 
					  '@parcel/transformer-sass': ^2.7.0
 | 
				
			||||||
 | 
					  '@parcel/transformer-glsl': ^2.7.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Build tools
 | 
				
			||||||
 | 
					  typescript: ^4.7.4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  sass: ^1.53.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  posthtml-extend: ^0.6.3
 | 
				
			||||||
 | 
					  posthtml-favicons: ^1.4.0
 | 
				
			||||||
 | 
					  posthtml-include: ^1.7.4
 | 
				
			||||||
 | 
					  posthtml-markdownit: ^1.3.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  '@babel/preset-env': ^7.18.6
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Type shims
 | 
				
			||||||
 | 
					  '@types/react-dom': ^18.0.6
 | 
				
			||||||
 | 
					  '@types/react-redux': ^7.1.24
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Dev tools
 | 
				
			||||||
 | 
					  npm-check-updates: ^16.0.5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  prettier: ^2.7.1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  typescript-language-server: ^0.11.2
 | 
				
			||||||
 | 
					  typescript-eslint-language-service: ^5.0.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  eslint: ^8.21.0
 | 
				
			||||||
 | 
					  '@typescript-eslint/parser': ^5.32.0
 | 
				
			||||||
 | 
					  '@typescript-eslint/eslint-plugin': ^5.32.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  vscode-langservers-extracted: ^4.2.1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					scripts:
 | 
				
			||||||
 | 
					  # Dev workflow
 | 
				
			||||||
 | 
					  build: parcel build --no-autoinstall
 | 
				
			||||||
 | 
					  serve: parcel serve --no-autoinstall
 | 
				
			||||||
 | 
					  watch: parcel watch --no-autoinstall
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Production build
 | 
				
			||||||
 | 
					  build-dist: parcel build --no-cache --no-autoinstall
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Checks
 | 
				
			||||||
 | 
					  check: tsc --noEmit
 | 
				
			||||||
 | 
					  style: prettier --check src
 | 
				
			||||||
 | 
					  lint: eslint --max-warnings=0 --format unix src
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Parcel config
 | 
				
			||||||
 | 
					source:
 | 
				
			||||||
 | 
					  - src/index.html
 | 
				
			||||||
 | 
					  - src/error.html
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					browserslist: '> 1%, not dead'
 | 
				
			||||||
							
								
								
									
										21
									
								
								templates/src/error.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								templates/src/error.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,21 @@
 | 
				
			||||||
 | 
					<extends src="./lib/html/base.html">
 | 
				
			||||||
 | 
					  <block name="content">
 | 
				
			||||||
 | 
					    <section class="section">
 | 
				
			||||||
 | 
					      <div class="container">
 | 
				
			||||||
 | 
					        <h1 class="title has-text-weight-normal">{{ error.status_code }}</h1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div class="columns">
 | 
				
			||||||
 | 
					          <div class="column content">
 | 
				
			||||||
 | 
					            <!-- prettier-ignore -->
 | 
				
			||||||
 | 
					            <markdown>
 | 
				
			||||||
 | 
					              {{ error.message }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              If you think this is a mistake, feel free to [contact
 | 
				
			||||||
 | 
					              me](~/src/mail.html)!
 | 
				
			||||||
 | 
					            </markdown>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </section>
 | 
				
			||||||
 | 
					  </block>
 | 
				
			||||||
 | 
					</extends>
 | 
				
			||||||
							
								
								
									
										78
									
								
								templates/src/icon.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								templates/src/icon.svg
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,78 @@
 | 
				
			||||||
 | 
					<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 | 
				
			||||||
 | 
					<svg
 | 
				
			||||||
 | 
					   xmlns:dc="http://purl.org/dc/elements/1.1/"
 | 
				
			||||||
 | 
					   xmlns:cc="http://creativecommons.org/ns#"
 | 
				
			||||||
 | 
					   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
 | 
				
			||||||
 | 
					   xmlns:svg="http://www.w3.org/2000/svg"
 | 
				
			||||||
 | 
					   xmlns="http://www.w3.org/2000/svg"
 | 
				
			||||||
 | 
					   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
 | 
				
			||||||
 | 
					   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
 | 
				
			||||||
 | 
					   version="1.0"
 | 
				
			||||||
 | 
					   width="260.000000pt"
 | 
				
			||||||
 | 
					   height="260.000000pt"
 | 
				
			||||||
 | 
					   viewBox="0 0 260.000000 260.000000"
 | 
				
			||||||
 | 
					   preserveAspectRatio="xMidYMid meet"
 | 
				
			||||||
 | 
					   id="svg70"
 | 
				
			||||||
 | 
					   sodipodi:docname="icon.svg"
 | 
				
			||||||
 | 
					   inkscape:version="1.0 (4035a4fb49, 2020-05-01)">
 | 
				
			||||||
 | 
					  <metadata
 | 
				
			||||||
 | 
					     id="metadata76">
 | 
				
			||||||
 | 
					    <rdf:RDF>
 | 
				
			||||||
 | 
					      <cc:Work
 | 
				
			||||||
 | 
					         rdf:about="">
 | 
				
			||||||
 | 
					        <dc:format>image/svg+xml</dc:format>
 | 
				
			||||||
 | 
					        <dc:type
 | 
				
			||||||
 | 
					           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
 | 
				
			||||||
 | 
					        <dc:title />
 | 
				
			||||||
 | 
					      </cc:Work>
 | 
				
			||||||
 | 
					    </rdf:RDF>
 | 
				
			||||||
 | 
					  </metadata>
 | 
				
			||||||
 | 
					  <defs
 | 
				
			||||||
 | 
					     id="defs74" />
 | 
				
			||||||
 | 
					  <sodipodi:namedview
 | 
				
			||||||
 | 
					     inkscape:pagecheckerboard="true"
 | 
				
			||||||
 | 
					     inkscape:document-rotation="0"
 | 
				
			||||||
 | 
					     pagecolor="#ffffff"
 | 
				
			||||||
 | 
					     bordercolor="#666666"
 | 
				
			||||||
 | 
					     borderopacity="1"
 | 
				
			||||||
 | 
					     objecttolerance="10"
 | 
				
			||||||
 | 
					     gridtolerance="10"
 | 
				
			||||||
 | 
					     guidetolerance="10"
 | 
				
			||||||
 | 
					     inkscape:pageopacity="0"
 | 
				
			||||||
 | 
					     inkscape:pageshadow="2"
 | 
				
			||||||
 | 
					     inkscape:window-width="1916"
 | 
				
			||||||
 | 
					     inkscape:window-height="1059"
 | 
				
			||||||
 | 
					     id="namedview72"
 | 
				
			||||||
 | 
					     showgrid="false"
 | 
				
			||||||
 | 
					     inkscape:zoom="2.2269231"
 | 
				
			||||||
 | 
					     inkscape:cx="173.33333"
 | 
				
			||||||
 | 
					     inkscape:cy="173.33333"
 | 
				
			||||||
 | 
					     inkscape:window-x="-2"
 | 
				
			||||||
 | 
					     inkscape:window-y="13"
 | 
				
			||||||
 | 
					     inkscape:window-maximized="0"
 | 
				
			||||||
 | 
					     inkscape:current-layer="layer1" />
 | 
				
			||||||
 | 
					  <g
 | 
				
			||||||
 | 
					     inkscape:groupmode="layer"
 | 
				
			||||||
 | 
					     id="layer1"
 | 
				
			||||||
 | 
					     inkscape:label="Background">
 | 
				
			||||||
 | 
					    <rect
 | 
				
			||||||
 | 
					       style="fill:#99d1ce;stroke-width:0.75;fill-opacity:1"
 | 
				
			||||||
 | 
					       id="rect843"
 | 
				
			||||||
 | 
					       width="260"
 | 
				
			||||||
 | 
					       height="260"
 | 
				
			||||||
 | 
					       x="2.0117849e-08"
 | 
				
			||||||
 | 
					       y="2.0117849e-08" />
 | 
				
			||||||
 | 
					  </g>
 | 
				
			||||||
 | 
					  <path
 | 
				
			||||||
 | 
					     style="fill:#0f0f0f;fill-opacity:1;stroke:#991dce;stroke-width:0.1"
 | 
				
			||||||
 | 
					     id="path62"
 | 
				
			||||||
 | 
					     d="M 2.0117848e-8,130 V 260 H 130 260 V 130 2.0117848e-8 H 130 2.0117848e-8 Z M 132.5,19.099999 c 1.4,1.300001 2,3.6 2.3,8.300001 l 0.4,6.4 8.2,0.699999 C 160.7,35.9 177.9,40.300001 182.89999,44.600002 188.9,49.6 189.1,58.399998 183.3,62.999999 180,65.6 176.6,65.5 161.7,62.499998 155,61.199999 145.99999,59.8 141.79999,59.399999 l -7.8,-0.700001 V 85.7 v 27 L 147,116.9 c 34.8,11.2 49.4,24.6 50.69999,46.39999 C 198.7,178 194.8,188.9 184.79999,199.5 174.6,210.4 162.6,216.5 144.89999,219.6 l -9.69999,1.7 -0.4,7.5 c -0.3,6.29999 -0.7,7.69999 -2.7,9.29999 -3.5,2.8 -8.5,2.5 -11,-0.69999 -1.7,-2.10001 -2.1,-4.1 -2.1,-9.4 v -6.7 l -7.1,-0.70001 c -8.8,-0.79999 -23.900001,-4 -34.400001,-7.3 -10.300001,-3.3 -15.5,-7.99999 -15.5,-14 0,-5.59999 3.5,-10.49999 8.2,-11.7 2.999999,-0.8 5.4,-0.39999 12.500002,1.7 C 93.099998,192.5 103,194.59999 112.3,195.5 l 6.7,0.7 v -29.1 c 0,-22.50001 -0.3,-29.20001 -1.20001,-29.5 C 93.099998,130.3 81.199997,124.9 72.900001,117.3 58.5,103.9 54.800001,82.499998 63.699999,64.300001 70.999998,49.399999 91.399998,37.199999 113.4,34.499999 L 119,33.8 v -5.7 c 0,-10.400001 7.1,-15.1 13.5,-9.000001 z" />
 | 
				
			||||||
 | 
					  <path
 | 
				
			||||||
 | 
					     style="fill:#0f0f0f;stroke:#991dce;stroke-width:0.1;fill-opacity:1"
 | 
				
			||||||
 | 
					     id="path64"
 | 
				
			||||||
 | 
					     d="m 113.2,60.6 c -18.4,4.7 -25.8,18.1 -17.7,32 1.3,2.2 4,5.3 6,6.8 3.7,2.9 16.4,9 17.1,8.3 0.3,-0.2 0.3,-11.2 0.2,-24.4 l -0.3,-24 z" />
 | 
				
			||||||
 | 
					  <path
 | 
				
			||||||
 | 
					     style="fill:#0f0f0f;stroke:#991dce;stroke-width:0.1;fill-opacity:1"
 | 
				
			||||||
 | 
					     id="path66"
 | 
				
			||||||
 | 
					     d="m 134,169.1 v 26.2 l 4.4,-0.5 c 9.6,-1.1 21.1,-8.9 24.2,-16.4 1.9,-4.6 1.7,-12.1 -0.5,-16.9 -2.6,-5.7 -10,-11.8 -18.6,-15.4 -4,-1.7 -7.8,-3.1 -8.4,-3.1 -0.8,0 -1.1,8.2 -1.1,26.1 z" />
 | 
				
			||||||
 | 
					</svg>
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 3.6 KiB  | 
							
								
								
									
										80
									
								
								templates/src/index.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								templates/src/index.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,80 @@
 | 
				
			||||||
 | 
					<extends src="./lib/html/base.html">
 | 
				
			||||||
 | 
					  <block name="content">
 | 
				
			||||||
 | 
					    <section class="section">
 | 
				
			||||||
 | 
					      <div class="container">
 | 
				
			||||||
 | 
					        <h1 class="title has-text-weight-normal is-family-monospace">
 | 
				
			||||||
 | 
					          $ <span id="typed-welcome"></span>
 | 
				
			||||||
 | 
					        </h1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <hr />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div class="columns">
 | 
				
			||||||
 | 
					          <div class="column content">
 | 
				
			||||||
 | 
					            <!-- prettier-ignore -->
 | 
				
			||||||
 | 
					            <markdown>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              ### About Me
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              Looks like you found my website. I suppose introductions are
 | 
				
			||||||
 | 
					              in order.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              My name's Tristan, I'm an avid Dutch-South African software
 | 
				
			||||||
 | 
					              consultant working in the UK. You probably either met me at an
 | 
				
			||||||
 | 
					              open source conference, a hackathon, a badminton session or at
 | 
				
			||||||
 | 
					              a roleplaying table.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              If not, well, this is also a great place to "meet" me. Have a
 | 
				
			||||||
 | 
					              nosey!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              ### This Website
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              There is not a whole lot here at the moment.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              You may find the following interesting though:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              - A [little web app](~/src/music_sample.html) showing
 | 
				
			||||||
 | 
					              off what WebGL can do in combination with the JavaScript
 | 
				
			||||||
 | 
					              Audio interface.
 | 
				
			||||||
 | 
					            </markdown>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <div class="column content">
 | 
				
			||||||
 | 
					            <!-- prettier-ignore -->
 | 
				
			||||||
 | 
					            <markdown>
 | 
				
			||||||
 | 
					              ### My Work
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              I'm a software consultant working for
 | 
				
			||||||
 | 
					              [Codethink](https://www.codethink.co.uk) in Manchester,
 | 
				
			||||||
 | 
					              UK. Our specializaiton is open source software, so this has
 | 
				
			||||||
 | 
					              allowed me to directly contribute to a number of open source
 | 
				
			||||||
 | 
					              projects, notably
 | 
				
			||||||
 | 
					              [BuildStream](https://www.gitlab.com/buildstream/buildstream),
 | 
				
			||||||
 | 
					              an integration tool for large software stacks.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              I've given a couple of talks on it, as well:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              - Build meetup 2017
 | 
				
			||||||
 | 
					              - Build meetup 2018
 | 
				
			||||||
 | 
					              - Build meetup 2019
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              Outside of work for Codethink, I'm generally interested in
 | 
				
			||||||
 | 
					              things such as NixOS and other tools that assist maintaining
 | 
				
			||||||
 | 
					              Linux systems - mostly born out of my pursuit of the perfect
 | 
				
			||||||
 | 
					              Linux desktop (feel free to have a browse through my
 | 
				
			||||||
 | 
					              [dotfiles](https://github.com/tlater/dotfiles)).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              I also just enjoy Programming, my core languages currently are
 | 
				
			||||||
 | 
					              Rust, Python, Lisp and JavaScript (including a number of
 | 
				
			||||||
 | 
					              frameworks and tools for these), although I have hopes to
 | 
				
			||||||
 | 
					              eventually reduce these to just Rust ;)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              If you're interested in seeing these things for yourself,
 | 
				
			||||||
 | 
					              visit my [Gitlab](https://gitlab.com/tlater) and
 | 
				
			||||||
 | 
					              [GitHub](https://github.com/tlater) pages.
 | 
				
			||||||
 | 
					            </markdown>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </section>
 | 
				
			||||||
 | 
					  </block>
 | 
				
			||||||
 | 
					</extends>
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								templates/src/lib/favicon.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								templates/src/lib/favicon.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 1.5 KiB  | 
							
								
								
									
										29
									
								
								templates/src/lib/html/base.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								templates/src/lib/html/base.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,29 @@
 | 
				
			||||||
 | 
					<!DOCTYPE html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					  <head>
 | 
				
			||||||
 | 
					    <meta charset="utf-8" />
 | 
				
			||||||
 | 
					    <meta name="description" content="tlater.net web server" />
 | 
				
			||||||
 | 
					    <meta name="author" contnet="Tristan Daniël Maat" />
 | 
				
			||||||
 | 
					    <meta name="viewport" content="width=device-width, initial-scale=1" />
 | 
				
			||||||
 | 
					    <link rel="icon" href="./icon.svg" type="image/x-icon" />
 | 
				
			||||||
 | 
					    <link rel="stylesheet" href="~/src/lib/scss/main.scss" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <block name="stylesheets"></block>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <title>tlater.net</title>
 | 
				
			||||||
 | 
					  </head>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <body>
 | 
				
			||||||
 | 
					    <block name="navigation">
 | 
				
			||||||
 | 
					      <include src="lib/html/navigation.html"></include>
 | 
				
			||||||
 | 
					    </block>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <include src="lib/html/message-flash.html"></include>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <block name="content"></block>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <block name="footer">
 | 
				
			||||||
 | 
					      <script type="module" src="lib/js/index.ts"></script>
 | 
				
			||||||
 | 
					    </block>
 | 
				
			||||||
 | 
					  </body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										8
									
								
								templates/src/lib/html/message-flash.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								templates/src/lib/html/message-flash.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,8 @@
 | 
				
			||||||
 | 
					<span>
 | 
				
			||||||
 | 
					  {{#if flash}}
 | 
				
			||||||
 | 
					  <div class="notification is-{{flash.type}}">
 | 
				
			||||||
 | 
					    <button class="delete" aria-label="Close"></button>
 | 
				
			||||||
 | 
					    <span role="alert"> {{ flash.message }} </span>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					  {{/if}}
 | 
				
			||||||
 | 
					</span>
 | 
				
			||||||
							
								
								
									
										23
									
								
								templates/src/lib/html/navigation.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								templates/src/lib/html/navigation.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,23 @@
 | 
				
			||||||
 | 
					<nav class="navbar is-dark" role="navigation" aria-label="main navigation">
 | 
				
			||||||
 | 
					  <div class="navbar-brand">
 | 
				
			||||||
 | 
					    <a class="navbar-item has-text-primary is-uppercase" href="/">tlater</a>
 | 
				
			||||||
 | 
					    <a
 | 
				
			||||||
 | 
					      class="navbar-burger"
 | 
				
			||||||
 | 
					      role="button"
 | 
				
			||||||
 | 
					      aria-label="menu"
 | 
				
			||||||
 | 
					      aria-expanded="false"
 | 
				
			||||||
 | 
					      data-target="main-navigation"
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					      <span aria-hidden="true"></span>
 | 
				
			||||||
 | 
					      <span aria-hidden="true"></span>
 | 
				
			||||||
 | 
					      <span aria-hidden="true"></span>
 | 
				
			||||||
 | 
					    </a>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					  <div id="main-navigation" class="navbar-menu">
 | 
				
			||||||
 | 
					    <div class="navbar-start">
 | 
				
			||||||
 | 
					      <a class="navbar-item" href="~/src/mail.html"> E-Mail </a>
 | 
				
			||||||
 | 
					      <a class="navbar-item" href="https://www.gitlab.com/tlater"> GitLab </a>
 | 
				
			||||||
 | 
					      <a class="navbar-item" href="https://www.github.com/TLATER"> GitHub </a>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					</nav>
 | 
				
			||||||
							
								
								
									
										67
									
								
								templates/src/lib/js/index.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								templates/src/lib/js/index.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,67 @@
 | 
				
			||||||
 | 
					function registerFlashCloseButtons() {
 | 
				
			||||||
 | 
					    const flashButtons = document.querySelectorAll(".notification .delete");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const flashButton of flashButtons) {
 | 
				
			||||||
 | 
					        if (flashButton.parentNode === null) {
 | 
				
			||||||
 | 
					            throw new Error("invalid flash button");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const flash = flashButton.parentNode;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        flash.addEventListener("click", () => {
 | 
				
			||||||
 | 
					            if (flash.parentNode === null) {
 | 
				
			||||||
 | 
					                throw new Error("invalid flash message");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            flash.parentNode.removeChild(flash);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // In development, there won't be a web server hooked up to
 | 
				
			||||||
 | 
					        // this to render the flash message, so we remove it entirely
 | 
				
			||||||
 | 
					        if (process.env.NODE_ENV === "development") {
 | 
				
			||||||
 | 
					            if (
 | 
				
			||||||
 | 
					                flash.parentNode === null ||
 | 
				
			||||||
 | 
					                flash.parentNode.parentNode === null
 | 
				
			||||||
 | 
					            ) {
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            console.warn("Disabling flash message");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Get the containing <span> element
 | 
				
			||||||
 | 
					            const block = flash.parentNode;
 | 
				
			||||||
 | 
					            flash.parentNode.parentNode.removeChild(block);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function registerNavCollapseButtons() {
 | 
				
			||||||
 | 
					    const navbarButtons = document.getElementsByClassName("navbar-burger");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const navbarButton of navbarButtons) {
 | 
				
			||||||
 | 
					        navbarButton.addEventListener("click", () => {
 | 
				
			||||||
 | 
					            if (
 | 
				
			||||||
 | 
					                !(navbarButton instanceof HTMLElement) ||
 | 
				
			||||||
 | 
					                !navbarButton.dataset.target
 | 
				
			||||||
 | 
					            ) {
 | 
				
			||||||
 | 
					                throw new Error("invalid navbar button");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const target = document.getElementById(navbarButton.dataset.target);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (target === null) {
 | 
				
			||||||
 | 
					                throw new Error("could not find navbar button target");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            navbarButton.classList.toggle("is-active");
 | 
				
			||||||
 | 
					            target.classList.toggle("is-active");
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					document.addEventListener("DOMContentLoaded", () => {
 | 
				
			||||||
 | 
					    registerFlashCloseButtons();
 | 
				
			||||||
 | 
					    registerNavCollapseButtons();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export {};
 | 
				
			||||||
							
								
								
									
										45
									
								
								templates/src/lib/scss/_custom-bulma.scss
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								templates/src/lib/scss/_custom-bulma.scss
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,45 @@
 | 
				
			||||||
 | 
					@use "sass:color";
 | 
				
			||||||
 | 
					@use "./_fonts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@import "~/node_modules/bulma/sass/utilities/initial-variables.sass";
 | 
				
			||||||
 | 
					@import "~/node_modules/bulma/sass/utilities/functions.sass";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$black: #0f0f0f;
 | 
				
			||||||
 | 
					$grey-darker: #11151c;
 | 
				
			||||||
 | 
					$grey-light: #dddddd;
 | 
				
			||||||
 | 
					$white: #ffffff;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$red: #dc322f;
 | 
				
			||||||
 | 
					$orange: #d26937;
 | 
				
			||||||
 | 
					$yellow: #b58900;
 | 
				
			||||||
 | 
					$blue: #195466;
 | 
				
			||||||
 | 
					$cyan: #599cab;
 | 
				
			||||||
 | 
					$green: #2aa889;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$primary: #99d1ce;
 | 
				
			||||||
 | 
					$link: $green;
 | 
				
			||||||
 | 
					$link-hover: color.scale($green, $lightness: +10%);
 | 
				
			||||||
 | 
					$link-active: color.scale($green, $lightness: +10%);
 | 
				
			||||||
 | 
					$link-focus: color.scale($green, $lightness: +10%);
 | 
				
			||||||
 | 
					$input-color: $grey-light;
 | 
				
			||||||
 | 
					$input-placeholder-color: $grey-light; // Some opacity is applied to this
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$weight-normal: 400;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$scheme-main: $black;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$family-sans-serif: Nunito, $family-sans-serif;
 | 
				
			||||||
 | 
					$family-monospace: Hack, $family-monospace;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$text: $grey-light;
 | 
				
			||||||
 | 
					$text-strong: $primary;
 | 
				
			||||||
 | 
					$label-color: $text;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$content-heading-color: $text;
 | 
				
			||||||
 | 
					$hr-background-color: $grey-light;
 | 
				
			||||||
 | 
					$hr-height: 1px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$pre-background: $grey-darker;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@import "~/node_modules/bulma";
 | 
				
			||||||
 | 
					@import "./_navbar";
 | 
				
			||||||
							
								
								
									
										46
									
								
								templates/src/lib/scss/_fonts.scss
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								templates/src/lib/scss/_fonts.scss
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,46 @@
 | 
				
			||||||
 | 
					@use "~/node_modules/@fontsource/nunito/scss/mixins" as Nunito;
 | 
				
			||||||
 | 
					@use "~/node_modules/@fontsource/arimo/scss/mixins" as Arimo;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$weights: 300, 400, 500, 600, 700;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@each $weight in $weights {
 | 
				
			||||||
 | 
					  @include Nunito.fontFace(
 | 
				
			||||||
 | 
					    $weight: $weight,
 | 
				
			||||||
 | 
					    $display: auto,
 | 
				
			||||||
 | 
					    $style: normal,
 | 
				
			||||||
 | 
					    $fontDir: "npm:@fontsource/nunito/files"
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @include Nunito.fontFace(
 | 
				
			||||||
 | 
					    $weight: $weight,
 | 
				
			||||||
 | 
					    $display: auto,
 | 
				
			||||||
 | 
					    $style: italic,
 | 
				
			||||||
 | 
					    $fontDir: "npm:@fontsource/nunito/files"
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@include Arimo.fontFace(
 | 
				
			||||||
 | 
					  $weight: 400,
 | 
				
			||||||
 | 
					  $display: auto,
 | 
				
			||||||
 | 
					  $style: normal,
 | 
				
			||||||
 | 
					  $fontDir: "npm:@fontsource/arimo/files"
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Hack *does* come with its own CSS, but it's broken and hasn't seen
 | 
				
			||||||
 | 
					// a release since https://github.com/source-foundry/Hack/issues/467
 | 
				
			||||||
 | 
					// was resolved.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$variants: regular 400 normal, bold 700 normal, italic 400 italic,
 | 
				
			||||||
 | 
					  bolditalic 700 italic;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@each $name, $weight, $style in $variants {
 | 
				
			||||||
 | 
					  @font-face {
 | 
				
			||||||
 | 
					    font-family: "Hack";
 | 
				
			||||||
 | 
					    src: url("npm:hack-font/build/web/fonts/hack-#{$name}-subset.woff2?sha=3114f1256")
 | 
				
			||||||
 | 
					        format("woff2"),
 | 
				
			||||||
 | 
					      url("npm:hack-font/build/web/fonts/hack-#{$name}-subset.woff?sha=3114f1256")
 | 
				
			||||||
 | 
					        format("woff");
 | 
				
			||||||
 | 
					    font-weight: $weight;
 | 
				
			||||||
 | 
					    font-style: $style;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										21
									
								
								templates/src/lib/scss/_navbar.scss
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								templates/src/lib/scss/_navbar.scss
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,21 @@
 | 
				
			||||||
 | 
					.navbar.is-dark {
 | 
				
			||||||
 | 
					  border: 1px solid #000000;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  & .navbar-brand > .navbar-item {
 | 
				
			||||||
 | 
					    font-family: Arimo;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    &:hover {
 | 
				
			||||||
 | 
					      background-color: $dark !important;
 | 
				
			||||||
 | 
					      color: $white !important;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  & .navbar-start > .navbar-item {
 | 
				
			||||||
 | 
					    color: rgba($white, 0.75);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    &:hover {
 | 
				
			||||||
 | 
					      background-color: $dark !important;
 | 
				
			||||||
 | 
					      color: $white !important;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										134
									
								
								templates/src/lib/scss/_typed.scss
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								templates/src/lib/scss/_typed.scss
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,134 @@
 | 
				
			||||||
 | 
					@use "sass:math";
 | 
				
			||||||
 | 
					@use "sass:list";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Animate a blinking cursor.
 | 
				
			||||||
 | 
					@mixin cursor($duration) {
 | 
				
			||||||
 | 
					  $name: cursor-09d03260130069771b6ddc1cb415f39fdd27ddfab7b01ba91273398c2d245ae4;
 | 
				
			||||||
 | 
					  // The number of times we need to blink is = the number of full
 | 
				
			||||||
 | 
					  // seconds (500ms * 2) that fit in the total duration, rounded up,
 | 
				
			||||||
 | 
					  // and doubled.
 | 
				
			||||||
 | 
					  $iterations: math.ceil(math.div($duration, 1s)) * 2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  animation: $name ease-in-out 500ms $iterations alternate;
 | 
				
			||||||
 | 
					  content: " ";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @keyframes #{$name} {
 | 
				
			||||||
 | 
					    from {
 | 
				
			||||||
 | 
					      content: " ";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    to {
 | 
				
			||||||
 | 
					      content: "█";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Animate a piece of text as if it was being typed by a human.
 | 
				
			||||||
 | 
					@mixin typed($text, $duration) {
 | 
				
			||||||
 | 
					  // We don't want a linearly typed set of text, which makes this
 | 
				
			||||||
 | 
					  // singificantly more complex.
 | 
				
			||||||
 | 
					  //
 | 
				
			||||||
 | 
					  // CSS animations normally do not permit per-frame changes in
 | 
				
			||||||
 | 
					  // duration (since the total animation time is fixed). This means we
 | 
				
			||||||
 | 
					  // need to create multiple animations, and delay them so that they
 | 
				
			||||||
 | 
					  // happen in the time sequence we want.
 | 
				
			||||||
 | 
					  //
 | 
				
			||||||
 | 
					  // We generate the raw values with _generate-animations, and then
 | 
				
			||||||
 | 
					  // split up the result into the animation API.
 | 
				
			||||||
 | 
					  $frames: str-length($text);
 | 
				
			||||||
 | 
					  $animations: _generate-animations($frames, 1.2s);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  animation-name: _unzip($animations, 1);
 | 
				
			||||||
 | 
					  animation-delay: _unzip($animations, 3);
 | 
				
			||||||
 | 
					  animation-fill-mode: forwards;
 | 
				
			||||||
 | 
					  content: "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // We need to type each character in separate animations, see above
 | 
				
			||||||
 | 
					  // comment.
 | 
				
			||||||
 | 
					  @each $name, $character in $animations {
 | 
				
			||||||
 | 
					    @keyframes #{$name} {
 | 
				
			||||||
 | 
					      from {
 | 
				
			||||||
 | 
					        content: str-slice($text, 0, $character);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      to {
 | 
				
			||||||
 | 
					        content: str-slice($text, 0, $character + 1);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Unzip a nested set of lists, taking the nth value of each sublist.
 | 
				
			||||||
 | 
					@function _unzip($lists, $i) {
 | 
				
			||||||
 | 
					  $out: ();
 | 
				
			||||||
 | 
					  $sep: comma;
 | 
				
			||||||
 | 
					  @each $sublist in $lists {
 | 
				
			||||||
 | 
					    $out: list.append($out, list.nth($sublist, $i), $sep);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  @return $out;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Compute the sum of all numbers in a list.
 | 
				
			||||||
 | 
					@function _sum($list) {
 | 
				
			||||||
 | 
					  $out: 0;
 | 
				
			||||||
 | 
					  @each $val in $list {
 | 
				
			||||||
 | 
					    $out: $out + $val;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  @return $out;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Produce a list from a shorter list by repeating it up until size
 | 
				
			||||||
 | 
					/// $length.
 | 
				
			||||||
 | 
					@function _round-robin($base, $length) {
 | 
				
			||||||
 | 
					  $out: ();
 | 
				
			||||||
 | 
					  $sep: list.separator($out);
 | 
				
			||||||
 | 
					  @for $i from 0 through $length {
 | 
				
			||||||
 | 
					    $out: list.append($out, list.nth($base, $i % list.length($base) + 1));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  @return $out;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Generate the actual animation values.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// This generates a nested list as:
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					///   (keyframe-name, index, start time)
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// The duration of each frame is taken from the internal $delays in a
 | 
				
			||||||
 | 
					/// round robin fashion, to give some amount of human-like variance to
 | 
				
			||||||
 | 
					/// the duration of each frame.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// Start time is set to the time at which the frame should start to
 | 
				
			||||||
 | 
					/// achieve the desired frame-by-frame duration.
 | 
				
			||||||
 | 
					@function _generate-animations($number, $total_duration) {
 | 
				
			||||||
 | 
					  $id: d66fa0449c0b4d4ca287f8c96428af928b2987b4d88b72b7d60152d9a55d9f29;
 | 
				
			||||||
 | 
					  $out: ();
 | 
				
			||||||
 | 
					  $sep: list.separator($out);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // A set of "human-like" delays for each typed character. In
 | 
				
			||||||
 | 
					  // practice, my typing seems to be about 20-70ms, but it looks a bit
 | 
				
			||||||
 | 
					  // nicer to increase all typing by 20ms to make the effect more
 | 
				
			||||||
 | 
					  // noticeable.
 | 
				
			||||||
 | 
					  //
 | 
				
			||||||
 | 
					  // Numbers generated once with a random number generator, rather
 | 
				
			||||||
 | 
					  // than using `math.random()`, since they end up in CSS verbatim,
 | 
				
			||||||
 | 
					  // and the build would be non-reproducible if we didn't do it this
 | 
				
			||||||
 | 
					  // way. Using `math.random() wouldn't change this dynamically each
 | 
				
			||||||
 | 
					  // time the page loads anyway, so we don't really lose anything by
 | 
				
			||||||
 | 
					  // pre-generating these numbers.
 | 
				
			||||||
 | 
					  $delays: 69ms, 83ms, 49ms, 48ms, 52ms, 59ms, 40ms, 71ms, 80ms, 67ms;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @for $animation from 0 through $number {
 | 
				
			||||||
 | 
					    $out: list.append(
 | 
				
			||||||
 | 
					      $out,
 | 
				
			||||||
 | 
					      (
 | 
				
			||||||
 | 
					        type-#{$id}-#{$animation},
 | 
				
			||||||
 | 
					        $animation,
 | 
				
			||||||
 | 
					        _sum(_round_robin($delays, $animation))
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					      $sep
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @return $out;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										18
									
								
								templates/src/lib/scss/main.scss
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								templates/src/lib/scss/main.scss
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,18 @@
 | 
				
			||||||
 | 
					@import "./_custom-bulma";
 | 
				
			||||||
 | 
					@import "./_typed";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					body {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  flex-direction: column;
 | 
				
			||||||
 | 
					  min-height: 100vh;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#typed-welcome {
 | 
				
			||||||
 | 
					  &::before {
 | 
				
			||||||
 | 
					    @include typed("Welcome to tlater.net!", 1.2s);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  &::after {
 | 
				
			||||||
 | 
					    @include cursor(6s);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										72
									
								
								templates/src/mail.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								templates/src/mail.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,72 @@
 | 
				
			||||||
 | 
					<extends src="./lib/html/base.html">
 | 
				
			||||||
 | 
					  <block name="content">
 | 
				
			||||||
 | 
					    <section class="section">
 | 
				
			||||||
 | 
					      <div class="container">
 | 
				
			||||||
 | 
					        <h1 class="title has-text-weight-normal">Contact Me</h1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div class="columns">
 | 
				
			||||||
 | 
					          <div class="column">
 | 
				
			||||||
 | 
					            <form id="sendmail" role="form" action="mail.html" method="post">
 | 
				
			||||||
 | 
					              <div class="field">
 | 
				
			||||||
 | 
					                <label class="label" for="mail">Email address</label>
 | 
				
			||||||
 | 
					                <input
 | 
				
			||||||
 | 
					                  id="mail"
 | 
				
			||||||
 | 
					                  class="input"
 | 
				
			||||||
 | 
					                  type="email"
 | 
				
			||||||
 | 
					                  placeholder="Your address"
 | 
				
			||||||
 | 
					                  name="mail"
 | 
				
			||||||
 | 
					                  required
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              <div class="field">
 | 
				
			||||||
 | 
					                <label class="label" for="subject">Subject</label>
 | 
				
			||||||
 | 
					                <input
 | 
				
			||||||
 | 
					                  id="subject"
 | 
				
			||||||
 | 
					                  class="input"
 | 
				
			||||||
 | 
					                  type="text"
 | 
				
			||||||
 | 
					                  placeholder="E.g. There's a typo on your home page!"
 | 
				
			||||||
 | 
					                  name="subject"
 | 
				
			||||||
 | 
					                  autocomplete="off"
 | 
				
			||||||
 | 
					                  required
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              <div class="field">
 | 
				
			||||||
 | 
					                <label class="label" for="message">Message</label>
 | 
				
			||||||
 | 
					                <textarea
 | 
				
			||||||
 | 
					                  id="message"
 | 
				
			||||||
 | 
					                  class="textarea"
 | 
				
			||||||
 | 
					                  type="text"
 | 
				
			||||||
 | 
					                  rows="6"
 | 
				
			||||||
 | 
					                  name="message"
 | 
				
			||||||
 | 
					                  autocomplete="off"
 | 
				
			||||||
 | 
					                  required
 | 
				
			||||||
 | 
					                ></textarea>
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              <div class="field">
 | 
				
			||||||
 | 
					                <div class="control">
 | 
				
			||||||
 | 
					                  <button class="button is-link">Send</button>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					            </form>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <div class="column content">
 | 
				
			||||||
 | 
					            <!-- prettier-ignore -->
 | 
				
			||||||
 | 
					            <markdown>
 | 
				
			||||||
 | 
					              Any messages you enter here are directly forwarded to me. I aim to
 | 
				
			||||||
 | 
					              respond within a day.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              Don't be upset about the form, I want to avoid the spam
 | 
				
			||||||
 | 
					              publishing your email address brings with it... And minimize
 | 
				
			||||||
 | 
					              the amount of mail that doesn't reach me, this form is an
 | 
				
			||||||
 | 
					              exception in all my spam filters, you see ;)
 | 
				
			||||||
 | 
					            </markdown>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </section>
 | 
				
			||||||
 | 
					  </block>
 | 
				
			||||||
 | 
					</extends>
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								templates/src/music/assets/Mseq_-_Journey.mp3
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								templates/src/music/assets/Mseq_-_Journey.mp3
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										2
									
								
								templates/src/music/assets/Mseq_-_Journey.mp3.d.ts
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								templates/src/music/assets/Mseq_-_Journey.mp3.d.ts
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,2 @@
 | 
				
			||||||
 | 
					declare const mseq: string;
 | 
				
			||||||
 | 
					export default mseq;
 | 
				
			||||||
							
								
								
									
										44
									
								
								templates/src/music/features/controls/Controls.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								templates/src/music/features/controls/Controls.tsx
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,44 @@
 | 
				
			||||||
 | 
					import React from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import Indicator from "../indicator/Indicator";
 | 
				
			||||||
 | 
					import { useAppSelector } from "../../hooks";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function Controls() {
 | 
				
			||||||
 | 
					    const title = useAppSelector((state) => state.musicPlayer.title);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let titleLine = <div className="level-item">{title.name}</div>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (title.name === "Journey" && title.artist === "Mseq") {
 | 
				
			||||||
 | 
					        titleLine = (
 | 
				
			||||||
 | 
					            <div className="level-item is-size-7-mobile is-flex-shrink-1">
 | 
				
			||||||
 | 
					                <div>
 | 
				
			||||||
 | 
					                    <a href="http://dig.ccmixter.org/files/Mseq/54702">
 | 
				
			||||||
 | 
					                        Journey
 | 
				
			||||||
 | 
					                    </a>
 | 
				
			||||||
 | 
					                     by Mseq (c) copyright 2016 Licensed under a Creative
 | 
				
			||||||
 | 
					                    Commons 
 | 
				
			||||||
 | 
					                    <a href="http://creativecommons.org/licenses/by-nc/3.0/">
 | 
				
			||||||
 | 
					                        Attribution Noncommercial (3.0)
 | 
				
			||||||
 | 
					                    </a>
 | 
				
			||||||
 | 
					                      license. Ft: Admiral Bob,Texas Radio Fish
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <div className="notification is-primary">
 | 
				
			||||||
 | 
					            <div className="level is-mobile">
 | 
				
			||||||
 | 
					                <div className="level-left is-flex-shrink-1">
 | 
				
			||||||
 | 
					                    <Indicator />
 | 
				
			||||||
 | 
					                    {titleLine}
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <div className="level-right is-hidden-mobile">
 | 
				
			||||||
 | 
					                    <div className="level-item">{title.artist}</div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Controls;
 | 
				
			||||||
							
								
								
									
										44
									
								
								templates/src/music/features/indicator/Indicator.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								templates/src/music/features/indicator/Indicator.tsx
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,44 @@
 | 
				
			||||||
 | 
					import React from "react";
 | 
				
			||||||
 | 
					import classNames from "classnames";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { useAppSelector, useAppDispatch } from "../../hooks";
 | 
				
			||||||
 | 
					import { togglePlay, PlayState } from "../musicplayer/musicPlayerSlice";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function Indicator() {
 | 
				
			||||||
 | 
					    const playing = useAppSelector((state) => state.musicPlayer.playing);
 | 
				
			||||||
 | 
					    const muted = useAppSelector((state) => state.musicPlayer.muted);
 | 
				
			||||||
 | 
					    const dispatch = useAppDispatch();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const buttonClass = classNames({
 | 
				
			||||||
 | 
					        button: true,
 | 
				
			||||||
 | 
					        "is-primary": true,
 | 
				
			||||||
 | 
					        "level-item": true,
 | 
				
			||||||
 | 
					        "is-loading": playing === PlayState.Loading,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const iconClass = classNames({
 | 
				
			||||||
 | 
					        fas: true,
 | 
				
			||||||
 | 
					        "fa-2x": true,
 | 
				
			||||||
 | 
					        "fa-muted": muted,
 | 
				
			||||||
 | 
					        "fa-play": playing === PlayState.Paused,
 | 
				
			||||||
 | 
					        "fa-pause": playing === PlayState.Playing,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <button
 | 
				
			||||||
 | 
					            type="button"
 | 
				
			||||||
 | 
					            onClick={() => {
 | 
				
			||||||
 | 
					                dispatch(togglePlay(null)).catch((e) => {
 | 
				
			||||||
 | 
					                    console.error(e);
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            }}
 | 
				
			||||||
 | 
					            className={buttonClass}
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					            <span className="icon is-medium">
 | 
				
			||||||
 | 
					                <i className={iconClass}></i>
 | 
				
			||||||
 | 
					            </span>
 | 
				
			||||||
 | 
					        </button>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Indicator;
 | 
				
			||||||
							
								
								
									
										17
									
								
								templates/src/music/features/musicplayer/MusicPlayer.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								templates/src/music/features/musicplayer/MusicPlayer.tsx
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,17 @@
 | 
				
			||||||
 | 
					import React from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import Controls from "../controls/Controls";
 | 
				
			||||||
 | 
					import Visualizer from "../visualizer/Visualizer";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function MusicPlayer() {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <div className="is-flex-grow-1 is-flex is-flex-direction-column">
 | 
				
			||||||
 | 
					            <Visualizer />
 | 
				
			||||||
 | 
					            <div className="is-flex-grow-0">
 | 
				
			||||||
 | 
					                <Controls />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default MusicPlayer;
 | 
				
			||||||
							
								
								
									
										157
									
								
								templates/src/music/features/musicplayer/musicPlayerSlice.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								templates/src/music/features/musicplayer/musicPlayerSlice.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,157 @@
 | 
				
			||||||
 | 
					import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit";
 | 
				
			||||||
 | 
					import { RootState, AppDispatch } from "../../store";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//************************
 | 
				
			||||||
 | 
					// Interface definitions *
 | 
				
			||||||
 | 
					//************************
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface MusicPlayerState {
 | 
				
			||||||
 | 
					    muted: boolean;
 | 
				
			||||||
 | 
					    playing: PlayState;
 | 
				
			||||||
 | 
					    title: MusicPlayerTitle;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface MusicPlayerTitle {
 | 
				
			||||||
 | 
					    source: string;
 | 
				
			||||||
 | 
					    artist: string;
 | 
				
			||||||
 | 
					    name: string;
 | 
				
			||||||
 | 
					    album: string;
 | 
				
			||||||
 | 
					    length: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum PlayState {
 | 
				
			||||||
 | 
					    Playing = "Playing",
 | 
				
			||||||
 | 
					    Paused = "Paused",
 | 
				
			||||||
 | 
					    Loading = "Loading",
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//*********************
 | 
				
			||||||
 | 
					// Music player logic *
 | 
				
			||||||
 | 
					//*********************
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MusicPlayer {
 | 
				
			||||||
 | 
					    private context?: AudioContext;
 | 
				
			||||||
 | 
					    private source: HTMLAudioElement;
 | 
				
			||||||
 | 
					    private sourceNode?: MediaElementAudioSourceNode;
 | 
				
			||||||
 | 
					    private volume?: GainNode;
 | 
				
			||||||
 | 
					    private analyser?: AnalyserNode;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor() {
 | 
				
			||||||
 | 
					        this.source = new Audio();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    get audioAnalyser() {
 | 
				
			||||||
 | 
					        return this.analyser;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    set src(source: string) {
 | 
				
			||||||
 | 
					        this.source.src = source;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    togglePlay = async (
 | 
				
			||||||
 | 
					        _: null,
 | 
				
			||||||
 | 
					        { getState }: { getState: () => RootState }
 | 
				
			||||||
 | 
					    ): Promise<PlayState> => {
 | 
				
			||||||
 | 
					        if (this.context === undefined) {
 | 
				
			||||||
 | 
					            this.context = new AudioContext();
 | 
				
			||||||
 | 
					            this.sourceNode = this.context.createMediaElementSource(
 | 
				
			||||||
 | 
					                this.source
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            this.volume = this.context.createGain();
 | 
				
			||||||
 | 
					            this.analyser = this.context.createAnalyser();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.analyser.fftSize = 2048;
 | 
				
			||||||
 | 
					            this.analyser.smoothingTimeConstant = 0.8;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.sourceNode.connect(this.analyser);
 | 
				
			||||||
 | 
					            this.sourceNode.connect(this.volume);
 | 
				
			||||||
 | 
					            this.volume.connect(this.context.destination);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const playing = getState().musicPlayer.playing;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        switch (playing) {
 | 
				
			||||||
 | 
					            case PlayState.Playing:
 | 
				
			||||||
 | 
					                this.source.pause();
 | 
				
			||||||
 | 
					                return PlayState.Paused;
 | 
				
			||||||
 | 
					            case PlayState.Paused:
 | 
				
			||||||
 | 
					            case PlayState.Loading:
 | 
				
			||||||
 | 
					                // Chrome's extra cookie, it refuses to play if we
 | 
				
			||||||
 | 
					                // don't resume after the first user interaction.
 | 
				
			||||||
 | 
					                await this.context.resume();
 | 
				
			||||||
 | 
					                return this.source.play().then(() => PlayState.Playing);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const player = new MusicPlayer();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//*************************
 | 
				
			||||||
 | 
					// Redux state management *
 | 
				
			||||||
 | 
					//*************************
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const initialState: MusicPlayerState = {
 | 
				
			||||||
 | 
					    muted: false,
 | 
				
			||||||
 | 
					    playing: PlayState.Paused,
 | 
				
			||||||
 | 
					    title: {
 | 
				
			||||||
 | 
					        source: "",
 | 
				
			||||||
 | 
					        artist: "",
 | 
				
			||||||
 | 
					        name: "",
 | 
				
			||||||
 | 
					        album: "",
 | 
				
			||||||
 | 
					        length: 0,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const musicPlayerSlice = createSlice({
 | 
				
			||||||
 | 
					    name: "musicPlayer",
 | 
				
			||||||
 | 
					    initialState,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    reducers: {
 | 
				
			||||||
 | 
					        setSource: (state, action: PayloadAction<MusicPlayerTitle>) => {
 | 
				
			||||||
 | 
					            state.title = action.payload;
 | 
				
			||||||
 | 
					            player.src = state.title.source;
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    extraReducers: (builder) => {
 | 
				
			||||||
 | 
					        builder
 | 
				
			||||||
 | 
					            .addCase(togglePlay.pending, (state) => {
 | 
				
			||||||
 | 
					                // If we are currently paused or loading, then this is
 | 
				
			||||||
 | 
					                // actually an async call, otherwise we just
 | 
				
			||||||
 | 
					                // synchronously pause the music.
 | 
				
			||||||
 | 
					                if (state.playing !== PlayState.Playing) {
 | 
				
			||||||
 | 
					                    state.playing = PlayState.Loading;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .addCase(togglePlay.fulfilled, (state, { payload }) => {
 | 
				
			||||||
 | 
					                state.playing = payload;
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .addCase(togglePlay.rejected, (state, { error }) => {
 | 
				
			||||||
 | 
					                if (error.message !== undefined) {
 | 
				
			||||||
 | 
					                    console.error(`Could not play music: ${error.message}`);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                state.playing = PlayState.Paused;
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const togglePlay = createAsyncThunk<
 | 
				
			||||||
 | 
					    PlayState,
 | 
				
			||||||
 | 
					    null,
 | 
				
			||||||
 | 
					    { dispatch: AppDispatch; state: RootState }
 | 
				
			||||||
 | 
					>("musicPlayer/togglePlay", player.togglePlay, {
 | 
				
			||||||
 | 
					    condition: (_, { getState }) => {
 | 
				
			||||||
 | 
					        const playing = getState().musicPlayer.playing;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (playing == PlayState.Loading) {
 | 
				
			||||||
 | 
					            // Block updates when we're loading
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const { setSource } = musicPlayerSlice.actions;
 | 
				
			||||||
 | 
					export { PlayState, player as musicPlayer };
 | 
				
			||||||
 | 
					export type { MusicPlayerState };
 | 
				
			||||||
 | 
					export default musicPlayerSlice.reducer;
 | 
				
			||||||
							
								
								
									
										342
									
								
								templates/src/music/features/visualizer/Renderer.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										342
									
								
								templates/src/music/features/visualizer/Renderer.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,342 @@
 | 
				
			||||||
 | 
					import { Shader } from "./Shader";
 | 
				
			||||||
 | 
					import { mat4 } from "gl-matrix";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Cube } from "./cube";
 | 
				
			||||||
 | 
					import vertexSource from "./shaders/vertices.glsl";
 | 
				
			||||||
 | 
					import fragmentSource from "./shaders/fragments.glsl";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ROTATION_SPEED = 0.0;
 | 
				
			||||||
 | 
					const BACKGROUND_COLOR = [0.0588235294118, 0.0588235294118, 0.0588235294118];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RendererError extends Error {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Renderer {
 | 
				
			||||||
 | 
					    private canvas: HTMLCanvasElement;
 | 
				
			||||||
 | 
					    private overlay: HTMLSpanElement;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private analyser: AnalyserNode;
 | 
				
			||||||
 | 
					    private analyserData: Uint8Array;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private lastFrameTime: number;
 | 
				
			||||||
 | 
					    private dTime: number;
 | 
				
			||||||
 | 
					    private nextAnimationFrame?: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private rotation: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private buffers: {
 | 
				
			||||||
 | 
					        indices?: WebGLBuffer;
 | 
				
			||||||
 | 
					        positions?: WebGLBuffer;
 | 
				
			||||||
 | 
					        normals?: WebGLBuffer;
 | 
				
			||||||
 | 
					        fft?: WebGLBuffer;
 | 
				
			||||||
 | 
					        velocitiesRead?: WebGLBuffer;
 | 
				
			||||||
 | 
					        velocitiesWrite?: WebGLBuffer;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor(
 | 
				
			||||||
 | 
					        analyser: AnalyserNode,
 | 
				
			||||||
 | 
					        canvas: HTMLCanvasElement,
 | 
				
			||||||
 | 
					        overlay: HTMLSpanElement
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        this.canvas = canvas;
 | 
				
			||||||
 | 
					        this.overlay = overlay;
 | 
				
			||||||
 | 
					        this.analyser = analyser;
 | 
				
			||||||
 | 
					        this.analyserData = new Uint8Array(analyser.frequencyBinCount);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.lastFrameTime = 0;
 | 
				
			||||||
 | 
					        this.dTime = 0;
 | 
				
			||||||
 | 
					        this.rotation = 0;
 | 
				
			||||||
 | 
					        this.buffers = {};
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    resizeAndDraw(
 | 
				
			||||||
 | 
					        gl: WebGL2RenderingContext,
 | 
				
			||||||
 | 
					        shader: Shader,
 | 
				
			||||||
 | 
					        observerData: ResizeObserverEntry | null
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        if (this.canvas.parentElement === null) {
 | 
				
			||||||
 | 
					            throw new Error("renderer has been removed from dom");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (this.nextAnimationFrame) {
 | 
				
			||||||
 | 
					            cancelAnimationFrame(this.nextAnimationFrame);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Note: For this to work, it's *incredibly important* for the
 | 
				
			||||||
 | 
					        // canvas to be overflowable by its parent, and its parent to
 | 
				
			||||||
 | 
					        // have `overflow: hidden` set. If using a flexbox, this means
 | 
				
			||||||
 | 
					        // that the canvas has to be `position: absolute`.
 | 
				
			||||||
 | 
					        let width: number;
 | 
				
			||||||
 | 
					        let height: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (observerData !== null && observerData.devicePixelContentBoxSize) {
 | 
				
			||||||
 | 
					            width = observerData.devicePixelContentBoxSize[0].inlineSize;
 | 
				
			||||||
 | 
					            height = observerData.devicePixelContentBoxSize[0].blockSize;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            // Fallback; the above API is even newer than
 | 
				
			||||||
 | 
					            // ResizeObserver, and by setting the observerData to null
 | 
				
			||||||
 | 
					            // we can manually resize at least once without going
 | 
				
			||||||
 | 
					            // through the API.
 | 
				
			||||||
 | 
					            if (this.canvas.parentElement === null) {
 | 
				
			||||||
 | 
					                throw new Error("canvas parent disappeared");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            // Note: This *requires* `box-sizing: border-box`
 | 
				
			||||||
 | 
					            ({ width, height } =
 | 
				
			||||||
 | 
					                this.canvas.parentElement.getBoundingClientRect());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.canvas.width = width;
 | 
				
			||||||
 | 
					        this.canvas.height = height;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        gl.viewport(0, 0, this.canvas.clientWidth, this.canvas.clientHeight);
 | 
				
			||||||
 | 
					        this.updateProjection(gl, shader);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // ResizeObserver will call when we should draw, so do our own
 | 
				
			||||||
 | 
					        // time calculation and draw the scene.
 | 
				
			||||||
 | 
					        this.updateTime(performance.now());
 | 
				
			||||||
 | 
					        this.drawScene(gl, shader);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    updateTime(time: number) {
 | 
				
			||||||
 | 
					        this.dTime = time - this.lastFrameTime;
 | 
				
			||||||
 | 
					        this.lastFrameTime = time;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    initializeScene() {
 | 
				
			||||||
 | 
					        if (this.canvas.parentElement === null) {
 | 
				
			||||||
 | 
					            throw new Error("canvas was not added to page");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const gl = this.canvas.getContext("webgl2");
 | 
				
			||||||
 | 
					        if (gl === null) {
 | 
				
			||||||
 | 
					            throw new RendererError("WebGL (2) is unsupported on this browser");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const shader = Shader.builder(gl)
 | 
				
			||||||
 | 
					            .addShader(vertexSource, gl.VERTEX_SHADER)
 | 
				
			||||||
 | 
					            .addShader(fragmentSource, gl.FRAGMENT_SHADER)
 | 
				
			||||||
 | 
					            .addAttribute("aVertexPosition")
 | 
				
			||||||
 | 
					            .addAttribute("aVertexNormal")
 | 
				
			||||||
 | 
					            .addAttribute("aHeight")
 | 
				
			||||||
 | 
					            .addUniforms("uProjectionMatrix")
 | 
				
			||||||
 | 
					            .addUniforms("uModelViewMatrix")
 | 
				
			||||||
 | 
					            .addUniforms("uNormalMatrix")
 | 
				
			||||||
 | 
					            .build();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.initGL(gl, shader);
 | 
				
			||||||
 | 
					        this.updateProjection(gl, shader);
 | 
				
			||||||
 | 
					        this.initBuffers(gl);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            const observer = new ResizeObserver((elements) => {
 | 
				
			||||||
 | 
					                // We only observe one element
 | 
				
			||||||
 | 
					                const element = elements[0];
 | 
				
			||||||
 | 
					                this.resizeAndDraw(gl, shader, element);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            observer.observe(this.canvas.parentElement);
 | 
				
			||||||
 | 
					        } catch (error) {
 | 
				
			||||||
 | 
					            // If the browser does not support ResizeObserver, we
 | 
				
			||||||
 | 
					            // simply don't resize. Resizing is hard enough, just use
 | 
				
			||||||
 | 
					            // a modern browser.
 | 
				
			||||||
 | 
					            if (error instanceof ReferenceError) {
 | 
				
			||||||
 | 
					                console.warn(
 | 
				
			||||||
 | 
					                    "Browser does not support `ResizeObserver`. Canvas resizing will be disabled."
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            } else throw error;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.resizeAndDraw(gl, shader, null);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    updateProjection(gl: WebGLRenderingContext, shader: Shader) {
 | 
				
			||||||
 | 
					        const projectionMatrix = mat4.create();
 | 
				
			||||||
 | 
					        mat4.perspective(
 | 
				
			||||||
 | 
					            projectionMatrix,
 | 
				
			||||||
 | 
					            (45 * Math.PI) / 180,
 | 
				
			||||||
 | 
					            gl.canvas.clientWidth / gl.canvas.clientHeight,
 | 
				
			||||||
 | 
					            0.1,
 | 
				
			||||||
 | 
					            100.0
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        gl.uniformMatrix4fv(
 | 
				
			||||||
 | 
					            shader.getUniform("uProjectionMatrix"),
 | 
				
			||||||
 | 
					            false,
 | 
				
			||||||
 | 
					            projectionMatrix
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    initBuffers(gl: WebGLRenderingContext) {
 | 
				
			||||||
 | 
					        // Scale down the unit cube before we use it
 | 
				
			||||||
 | 
					        Cube.vertices = Cube.vertices.map(
 | 
				
			||||||
 | 
					            (num: number) => num / this.analyser.frequencyBinCount
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Position buffer
 | 
				
			||||||
 | 
					        const positionBuffer = gl.createBuffer();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (positionBuffer === null) {
 | 
				
			||||||
 | 
					            throw new Error("could not initialize position buffer");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
 | 
				
			||||||
 | 
					        gl.bufferData(gl.ARRAY_BUFFER, Cube.vertices, gl.STATIC_DRAW);
 | 
				
			||||||
 | 
					        this.buffers.positions = positionBuffer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Index buffer
 | 
				
			||||||
 | 
					        const indexBuffer = gl.createBuffer();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (indexBuffer === null) {
 | 
				
			||||||
 | 
					            throw new Error("could not initialize index buffer");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
 | 
				
			||||||
 | 
					        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, Cube.indices, gl.STATIC_DRAW);
 | 
				
			||||||
 | 
					        this.buffers.indices = indexBuffer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Surface normal buffer
 | 
				
			||||||
 | 
					        const normalBuffer = gl.createBuffer();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (normalBuffer === null) {
 | 
				
			||||||
 | 
					            throw new Error("could not initialize normal buffer");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
 | 
				
			||||||
 | 
					        gl.bufferData(gl.ARRAY_BUFFER, Cube.normals, gl.STATIC_DRAW);
 | 
				
			||||||
 | 
					        this.buffers.normals = normalBuffer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // fft data buffer
 | 
				
			||||||
 | 
					        const fftBuffer = gl.createBuffer();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (fftBuffer === null) {
 | 
				
			||||||
 | 
					            throw new Error("could not initialize fft buffer");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // No need to initialize this buffer here since we will be
 | 
				
			||||||
 | 
					        // updating it as soon as we start rendering anyway.
 | 
				
			||||||
 | 
					        this.buffers.fft = fftBuffer;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    initGL(gl: WebGLRenderingContext, shader: Shader) {
 | 
				
			||||||
 | 
					        gl.useProgram(shader.program);
 | 
				
			||||||
 | 
					        gl.clearColor(
 | 
				
			||||||
 | 
					            BACKGROUND_COLOR[0],
 | 
				
			||||||
 | 
					            BACKGROUND_COLOR[1],
 | 
				
			||||||
 | 
					            BACKGROUND_COLOR[2],
 | 
				
			||||||
 | 
					            1.0
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        gl.clearDepth(1.0);
 | 
				
			||||||
 | 
					        gl.enable(gl.DEPTH_TEST);
 | 
				
			||||||
 | 
					        gl.depthFunc(gl.LESS);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    updateMatrices(gl: WebGLRenderingContext, shader: Shader) {
 | 
				
			||||||
 | 
					        this.rotation += (this.dTime / 1000.0) * ROTATION_SPEED;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const modelViewMatrix = mat4.create();
 | 
				
			||||||
 | 
					        mat4.translate(modelViewMatrix, modelViewMatrix, [
 | 
				
			||||||
 | 
					            0.0,
 | 
				
			||||||
 | 
					            0.025,
 | 
				
			||||||
 | 
					            -((this.analyser.frequencyBinCount / gl.canvas.clientWidth) * 3),
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					        mat4.rotateX(modelViewMatrix, modelViewMatrix, Math.PI / 16);
 | 
				
			||||||
 | 
					        mat4.rotateY(modelViewMatrix, modelViewMatrix, this.rotation);
 | 
				
			||||||
 | 
					        mat4.translate(modelViewMatrix, modelViewMatrix, [-1.0, 0.0, 0.0]);
 | 
				
			||||||
 | 
					        gl.uniformMatrix4fv(
 | 
				
			||||||
 | 
					            shader.getUniform("uModelViewMatrix"),
 | 
				
			||||||
 | 
					            false,
 | 
				
			||||||
 | 
					            modelViewMatrix
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const normalMatrix = mat4.create();
 | 
				
			||||||
 | 
					        mat4.invert(normalMatrix, modelViewMatrix);
 | 
				
			||||||
 | 
					        mat4.transpose(normalMatrix, normalMatrix);
 | 
				
			||||||
 | 
					        gl.uniformMatrix4fv(
 | 
				
			||||||
 | 
					            shader.getUniform("uNormalMatrix"),
 | 
				
			||||||
 | 
					            false,
 | 
				
			||||||
 | 
					            normalMatrix
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    updateBuffers(gl: WebGL2RenderingContext, shader: Shader) {
 | 
				
			||||||
 | 
					        if (
 | 
				
			||||||
 | 
					            this.buffers.indices === undefined ||
 | 
				
			||||||
 | 
					            this.buffers.positions === undefined ||
 | 
				
			||||||
 | 
					            this.buffers.normals === undefined ||
 | 
				
			||||||
 | 
					            this.buffers.fft === undefined
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					            throw new Error("failed to create buffers before rendering");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Update cube buffers
 | 
				
			||||||
 | 
					        gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers.positions);
 | 
				
			||||||
 | 
					        gl.vertexAttribPointer(
 | 
				
			||||||
 | 
					            shader.getAttribute("aVertexPosition"),
 | 
				
			||||||
 | 
					            3,
 | 
				
			||||||
 | 
					            gl.FLOAT,
 | 
				
			||||||
 | 
					            false,
 | 
				
			||||||
 | 
					            0,
 | 
				
			||||||
 | 
					            0
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        gl.enableVertexAttribArray(shader.getAttribute("aVertexPosition"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.buffers.indices);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers.normals);
 | 
				
			||||||
 | 
					        gl.vertexAttribPointer(
 | 
				
			||||||
 | 
					            shader.getAttribute("aVertexNormal"),
 | 
				
			||||||
 | 
					            3,
 | 
				
			||||||
 | 
					            gl.FLOAT,
 | 
				
			||||||
 | 
					            false,
 | 
				
			||||||
 | 
					            0,
 | 
				
			||||||
 | 
					            0
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        gl.enableVertexAttribArray(shader.getAttribute("aVertexNormal"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Update fft
 | 
				
			||||||
 | 
					        this.analyser.getByteFrequencyData(this.analyserData);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers.fft);
 | 
				
			||||||
 | 
					        gl.bufferData(gl.ARRAY_BUFFER, this.analyserData, gl.STREAM_DRAW);
 | 
				
			||||||
 | 
					        gl.vertexAttribPointer(
 | 
				
			||||||
 | 
					            shader.getAttribute("aHeight"),
 | 
				
			||||||
 | 
					            1,
 | 
				
			||||||
 | 
					            gl.UNSIGNED_BYTE,
 | 
				
			||||||
 | 
					            false,
 | 
				
			||||||
 | 
					            0,
 | 
				
			||||||
 | 
					            0
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        gl.vertexAttribDivisor(shader.getAttribute("aHeight"), 1);
 | 
				
			||||||
 | 
					        gl.enableVertexAttribArray(shader.getAttribute("aHeight"));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    drawScene(gl: WebGL2RenderingContext, shader: Shader) {
 | 
				
			||||||
 | 
					        this.updateMatrices(gl, shader);
 | 
				
			||||||
 | 
					        this.updateBuffers(gl, shader);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let cpuTime = 0;
 | 
				
			||||||
 | 
					        if (process.env.NODE_ENV === "development") {
 | 
				
			||||||
 | 
					            cpuTime = Math.round(performance.now() - this.lastFrameTime);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
 | 
				
			||||||
 | 
					        gl.drawElementsInstanced(
 | 
				
			||||||
 | 
					            gl.TRIANGLES,
 | 
				
			||||||
 | 
					            36,
 | 
				
			||||||
 | 
					            gl.UNSIGNED_SHORT,
 | 
				
			||||||
 | 
					            0,
 | 
				
			||||||
 | 
					            this.analyser.frequencyBinCount
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (process.env.NODE_ENV === "development") {
 | 
				
			||||||
 | 
					            const gpuTime = Math.round(performance.now() - this.lastFrameTime);
 | 
				
			||||||
 | 
					            const dTime = Math.round(this.dTime);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.overlay.innerText = `${dTime}ms (${cpuTime}ms / ${gpuTime}ms)`;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.nextAnimationFrame = requestAnimationFrame((time) => {
 | 
				
			||||||
 | 
					            this.updateTime(time);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.drawScene(gl, shader);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export { Renderer, RendererError };
 | 
				
			||||||
							
								
								
									
										183
									
								
								templates/src/music/features/visualizer/Shader.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								templates/src/music/features/visualizer/Shader.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,183 @@
 | 
				
			||||||
 | 
					type ShaderType =
 | 
				
			||||||
 | 
					    | WebGLRenderingContext["VERTEX_SHADER"]
 | 
				
			||||||
 | 
					    | WebGLRenderingContext["FRAGMENT_SHADER"];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface ShaderSource {
 | 
				
			||||||
 | 
					    source: string;
 | 
				
			||||||
 | 
					    kind: ShaderType;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ShaderAttributes = Map<string, number>;
 | 
				
			||||||
 | 
					type ShaderUniforms = Map<string, WebGLUniformLocation>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ShaderError extends Error {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Shader {
 | 
				
			||||||
 | 
					    private program_: WebGLProgram;
 | 
				
			||||||
 | 
					    private attributes_: ShaderAttributes;
 | 
				
			||||||
 | 
					    private uniforms_: ShaderUniforms;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor(
 | 
				
			||||||
 | 
					        program: WebGLProgram,
 | 
				
			||||||
 | 
					        attributes: ShaderAttributes,
 | 
				
			||||||
 | 
					        uniforms: ShaderUniforms
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        this.program_ = program;
 | 
				
			||||||
 | 
					        this.attributes_ = attributes;
 | 
				
			||||||
 | 
					        this.uniforms_ = uniforms;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static builder(gl: WebGLRenderingContext): ShaderBuilder {
 | 
				
			||||||
 | 
					        return new ShaderBuilder(gl);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    get program(): WebGLProgram {
 | 
				
			||||||
 | 
					        return this.program_;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public getAttribute(name: string): number {
 | 
				
			||||||
 | 
					        const attribute = this.attributes_.get(name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (attribute === undefined) {
 | 
				
			||||||
 | 
					            throw new ShaderError(`undefined shader attribute: ${name}`);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return attribute;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public getUniform(name: string): WebGLUniformLocation {
 | 
				
			||||||
 | 
					        const uniform = this.uniforms_.get(name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (uniform === undefined) {
 | 
				
			||||||
 | 
					            throw new ShaderError(`undefined shader uniform: ${name}`);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return uniform;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    get uniforms(): ShaderUniforms {
 | 
				
			||||||
 | 
					        return this.uniforms_;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ShaderBuilder {
 | 
				
			||||||
 | 
					    private gl: WebGLRenderingContext;
 | 
				
			||||||
 | 
					    private sources: Array<ShaderSource>;
 | 
				
			||||||
 | 
					    private attributes: Array<string>;
 | 
				
			||||||
 | 
					    private uniforms: Array<string>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public constructor(gl: WebGLRenderingContext) {
 | 
				
			||||||
 | 
					        this.gl = gl;
 | 
				
			||||||
 | 
					        this.sources = new Array<ShaderSource>();
 | 
				
			||||||
 | 
					        this.attributes = new Array<string>();
 | 
				
			||||||
 | 
					        this.uniforms = new Array<string>();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public addShader(source: string, kind: ShaderType): ShaderBuilder {
 | 
				
			||||||
 | 
					        this.sources.push({ source, kind });
 | 
				
			||||||
 | 
					        return this;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public addAttribute(name: string): ShaderBuilder {
 | 
				
			||||||
 | 
					        this.attributes.push(name);
 | 
				
			||||||
 | 
					        return this;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public addUniforms(name: string): ShaderBuilder {
 | 
				
			||||||
 | 
					        this.uniforms.push(name);
 | 
				
			||||||
 | 
					        return this;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public build(): Shader {
 | 
				
			||||||
 | 
					        // Load, compile and link shader sources
 | 
				
			||||||
 | 
					        const shaders = this.sources.map(({ source, kind }) => {
 | 
				
			||||||
 | 
					            return this.loadShader(source, kind);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const shaderProgram = this.gl.createProgram();
 | 
				
			||||||
 | 
					        if (shaderProgram === null) {
 | 
				
			||||||
 | 
					            throw new ShaderError("failed to create shader program");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (const shader of shaders) {
 | 
				
			||||||
 | 
					            this.gl.attachShader(shaderProgram, shader);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.gl.linkProgram(shaderProgram);
 | 
				
			||||||
 | 
					        if (!this.gl.getProgramParameter(shaderProgram, this.gl.LINK_STATUS)) {
 | 
				
			||||||
 | 
					            let message = "failed to link shader program";
 | 
				
			||||||
 | 
					            const log = this.gl.getProgramInfoLog(shaderProgram);
 | 
				
			||||||
 | 
					            if (log !== null) {
 | 
				
			||||||
 | 
					                message = `failed to link shader program: ${log}`;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            throw new ShaderError(message);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Find attribute and uniform locations
 | 
				
			||||||
 | 
					        const attributes = this.attributes.reduce((acc, attribute) => {
 | 
				
			||||||
 | 
					            const attributeLocation = this.gl.getAttribLocation(
 | 
				
			||||||
 | 
					                shaderProgram,
 | 
				
			||||||
 | 
					                attribute
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (attributeLocation === -1) {
 | 
				
			||||||
 | 
					                throw new ShaderError(
 | 
				
			||||||
 | 
					                    `shader attribute '${attribute}' could not be found`
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return new Map<string, number>([
 | 
				
			||||||
 | 
					                ...acc,
 | 
				
			||||||
 | 
					                [attribute, attributeLocation],
 | 
				
			||||||
 | 
					            ]);
 | 
				
			||||||
 | 
					        }, new Map<string, number>());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const uniforms = this.uniforms.reduce((acc, uniform) => {
 | 
				
			||||||
 | 
					            const uniformLocation = this.gl.getUniformLocation(
 | 
				
			||||||
 | 
					                shaderProgram,
 | 
				
			||||||
 | 
					                uniform
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (uniformLocation === null) {
 | 
				
			||||||
 | 
					                throw new ShaderError(
 | 
				
			||||||
 | 
					                    `shader uniform '${uniform}' could not be found`
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return new Map<string, WebGLUniformLocation>([
 | 
				
			||||||
 | 
					                ...acc,
 | 
				
			||||||
 | 
					                [uniform, uniformLocation],
 | 
				
			||||||
 | 
					            ]);
 | 
				
			||||||
 | 
					        }, new Map<string, WebGLUniformLocation>());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Build actual shader object
 | 
				
			||||||
 | 
					        return new Shader(shaderProgram, attributes, uniforms);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private loadShader(source: string, kind: ShaderType): WebGLShader {
 | 
				
			||||||
 | 
					        const shader = this.gl.createShader(kind);
 | 
				
			||||||
 | 
					        if (shader === null) {
 | 
				
			||||||
 | 
					            throw new ShaderError(`failed to initialize shader "${source}"`);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.gl.shaderSource(shader, source);
 | 
				
			||||||
 | 
					        this.gl.compileShader(shader);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {
 | 
				
			||||||
 | 
					            let message = `failed to compile shader "${source}"`;
 | 
				
			||||||
 | 
					            const log = this.gl.getShaderInfoLog(shader);
 | 
				
			||||||
 | 
					            if (log !== null) {
 | 
				
			||||||
 | 
					                message = `failed to compile shader "${source}": ${log}`;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.gl.deleteShader(shader);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            throw new ShaderError(message);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return shader;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export { Shader, ShaderError };
 | 
				
			||||||
							
								
								
									
										139
									
								
								templates/src/music/features/visualizer/Visualizer.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								templates/src/music/features/visualizer/Visualizer.tsx
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,139 @@
 | 
				
			||||||
 | 
					import React, { useCallback, useState } from "react";
 | 
				
			||||||
 | 
					import { Renderer, RendererError } from "./Renderer";
 | 
				
			||||||
 | 
					import { ShaderError } from "./Shader";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { useAppSelector } from "../../hooks";
 | 
				
			||||||
 | 
					import { PlayState, musicPlayer } from "../musicplayer/musicPlayerSlice";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function Visualizer() {
 | 
				
			||||||
 | 
					    const playing = useAppSelector((state) => state.musicPlayer.playing);
 | 
				
			||||||
 | 
					    const rendererState = useState<Renderer | null>(null);
 | 
				
			||||||
 | 
					    let renderer = rendererState[0];
 | 
				
			||||||
 | 
					    const setRenderer = rendererState[1];
 | 
				
			||||||
 | 
					    const [renderError, setRenderError] = useState<JSX.Element | null>(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const visualizer = useCallback(
 | 
				
			||||||
 | 
					        (visualizer: HTMLDivElement | null) => {
 | 
				
			||||||
 | 
					            // TODO(tlater): Clean up state management. This is all
 | 
				
			||||||
 | 
					            // but trivial; there's seemingly no good place to keep
 | 
				
			||||||
 | 
					            // these big api objects (WebGLRenderingcontext or
 | 
				
			||||||
 | 
					            // AudioContext).
 | 
				
			||||||
 | 
					            //
 | 
				
			||||||
 | 
					            // It's tricky, too, because obviously react expects to be
 | 
				
			||||||
 | 
					            // in control of the DOM, and be allowed to delete our
 | 
				
			||||||
 | 
					            // canvas and create a new one.
 | 
				
			||||||
 | 
					            //
 | 
				
			||||||
 | 
					            // For the moment, this works, but it's a definite hack.
 | 
				
			||||||
 | 
					            if (renderer) {
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Until we start playing music, there is nothing to render.
 | 
				
			||||||
 | 
					            if (playing !== PlayState.Playing) {
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (musicPlayer.audioAnalyser === undefined) {
 | 
				
			||||||
 | 
					                throw new Error("MusicPlayer analyser was not set up on time");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // If we're rendering an error message, we won't be
 | 
				
			||||||
 | 
					            // setting up the visualizer.
 | 
				
			||||||
 | 
					            //
 | 
				
			||||||
 | 
					            // Also, nonintuitively, renderError will be null here on
 | 
				
			||||||
 | 
					            // subsequent iterations, so we can't rely on it to
 | 
				
			||||||
 | 
					            // identify errors.
 | 
				
			||||||
 | 
					            if (visualizer === null) {
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const canvas = visualizer.children[0];
 | 
				
			||||||
 | 
					            const overlay = visualizer.children[1];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (
 | 
				
			||||||
 | 
					                !(canvas instanceof HTMLCanvasElement) ||
 | 
				
			||||||
 | 
					                !(overlay instanceof HTMLSpanElement)
 | 
				
			||||||
 | 
					            ) {
 | 
				
			||||||
 | 
					                throw new Error(
 | 
				
			||||||
 | 
					                    "react did not create our visualizer div correctly"
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (renderer === null) {
 | 
				
			||||||
 | 
					                renderer = new Renderer(
 | 
				
			||||||
 | 
					                    musicPlayer.audioAnalyser,
 | 
				
			||||||
 | 
					                    canvas,
 | 
				
			||||||
 | 
					                    overlay
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					                setRenderer(renderer);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                renderer.initializeScene();
 | 
				
			||||||
 | 
					            } catch (error) {
 | 
				
			||||||
 | 
					                // Log so we don't lose the stack trace
 | 
				
			||||||
 | 
					                console.log(error);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (error instanceof ShaderError) {
 | 
				
			||||||
 | 
					                    setRenderError(
 | 
				
			||||||
 | 
					                        <span>
 | 
				
			||||||
 | 
					                            Failed to compile shader; This is a bug, feel free
 | 
				
			||||||
 | 
					                            to contact me with this error message:
 | 
				
			||||||
 | 
					                            <pre>
 | 
				
			||||||
 | 
					                                <code className="has-text-danger">
 | 
				
			||||||
 | 
					                                    {error.message}
 | 
				
			||||||
 | 
					                                </code>
 | 
				
			||||||
 | 
					                            </pre>
 | 
				
			||||||
 | 
					                        </span>
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                } else if (error instanceof RendererError) {
 | 
				
			||||||
 | 
					                    setRenderError(
 | 
				
			||||||
 | 
					                        <span>
 | 
				
			||||||
 | 
					                            This browser does not support WebGL 2, sadly. This
 | 
				
			||||||
 | 
					                            demo uses WebGL and specifically instanced drawing,
 | 
				
			||||||
 | 
					                            so unfortunately this means it can't run on your
 | 
				
			||||||
 | 
					                            browser/device.
 | 
				
			||||||
 | 
					                        </span>
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                } else if (error instanceof Error) {
 | 
				
			||||||
 | 
					                    setRenderError(
 | 
				
			||||||
 | 
					                        <span>
 | 
				
			||||||
 | 
					                            Something went very wrong; apologies, either your
 | 
				
			||||||
 | 
					                            browser is not behaving or there's a serious bug.
 | 
				
			||||||
 | 
					                            You can contact me with this error message:
 | 
				
			||||||
 | 
					                            <pre>
 | 
				
			||||||
 | 
					                                <code className="has-text-danger">
 | 
				
			||||||
 | 
					                                    {error.message}
 | 
				
			||||||
 | 
					                                </code>
 | 
				
			||||||
 | 
					                            </pre>
 | 
				
			||||||
 | 
					                        </span>
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    setRenderError(
 | 
				
			||||||
 | 
					                        <span>
 | 
				
			||||||
 | 
					                            Something went very wrong; apologies, either your
 | 
				
			||||||
 | 
					                            browser is not behaving or there's a serious bug.
 | 
				
			||||||
 | 
					                        </span>
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        [playing, renderer, musicPlayer.audioAnalyser]
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (renderError === null) {
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
 | 
					            <div
 | 
				
			||||||
 | 
					                ref={visualizer}
 | 
				
			||||||
 | 
					                className="is-flex-grow-1 is-clipped is-relative"
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					                <canvas className="is-block is-absolute is-border-box"></canvas>
 | 
				
			||||||
 | 
					                <span className="is-bottom-left"></span>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        return renderError;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Visualizer;
 | 
				
			||||||
							
								
								
									
										84
									
								
								templates/src/music/features/visualizer/cube.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								templates/src/music/features/visualizer/cube.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,84 @@
 | 
				
			||||||
 | 
					/**  * A hand-written 3d model of a cube.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * If this ever needs to be more than this, consider moving it to a
 | 
				
			||||||
 | 
					 * proper .obj model.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					const Cube = {
 | 
				
			||||||
 | 
					    // prettier-ignore
 | 
				
			||||||
 | 
					    vertices: new Float32Array([
 | 
				
			||||||
 | 
					       -1.0, -1.0,  1.0,
 | 
				
			||||||
 | 
					        1.0, -1.0,  1.0,
 | 
				
			||||||
 | 
					        1.0,  1.0,  1.0,
 | 
				
			||||||
 | 
					       -1.0,  1.0,  1.0,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       -1.0, -1.0, -1.0,
 | 
				
			||||||
 | 
					       -1.0,  1.0, -1.0,
 | 
				
			||||||
 | 
					        1.0,  1.0, -1.0,
 | 
				
			||||||
 | 
					        1.0, -1.0, -1.0,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       -1.0,  1.0, -1.0,
 | 
				
			||||||
 | 
					       -1.0,  1.0,  1.0,
 | 
				
			||||||
 | 
					        1.0,  1.0,  1.0,
 | 
				
			||||||
 | 
					        1.0,  1.0, -1.0,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       -1.0, -1.0, -1.0,
 | 
				
			||||||
 | 
					        1.0, -1.0, -1.0,
 | 
				
			||||||
 | 
					        1.0, -1.0,  1.0,
 | 
				
			||||||
 | 
					       -1.0, -1.0,  1.0,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        1.0, -1.0, -1.0,
 | 
				
			||||||
 | 
					        1.0,  1.0, -1.0,
 | 
				
			||||||
 | 
					        1.0,  1.0,  1.0,
 | 
				
			||||||
 | 
					        1.0, -1.0,  1.0,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       -1.0, -1.0, -1.0,
 | 
				
			||||||
 | 
					       -1.0, -1.0,  1.0,
 | 
				
			||||||
 | 
					       -1.0,  1.0,  1.0,
 | 
				
			||||||
 | 
					       -1.0,  1.0, -1.0,
 | 
				
			||||||
 | 
					    ]),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // prettier-ignore
 | 
				
			||||||
 | 
					    indices: new Uint16Array([
 | 
				
			||||||
 | 
					        0,  1,  2,   0,  2,  3,
 | 
				
			||||||
 | 
					        4,  5,  6,   4,  6,  7,
 | 
				
			||||||
 | 
					        8,  9, 10,   8, 10, 11,
 | 
				
			||||||
 | 
					        12, 13, 14,  12, 14, 15,
 | 
				
			||||||
 | 
					        16, 17, 18,  16, 18, 19,
 | 
				
			||||||
 | 
					        20, 21, 22,  20, 22, 23,
 | 
				
			||||||
 | 
					    ]),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // prettier-ignore
 | 
				
			||||||
 | 
					    normals: new Float32Array([
 | 
				
			||||||
 | 
					        0.0,  0.0,  1.0,
 | 
				
			||||||
 | 
					        0.0,  0.0,  1.0,
 | 
				
			||||||
 | 
					        0.0,  0.0,  1.0,
 | 
				
			||||||
 | 
					        0.0,  0.0,  1.0,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        0.0,  0.0, -1.0,
 | 
				
			||||||
 | 
					        0.0,  0.0, -1.0,
 | 
				
			||||||
 | 
					        0.0,  0.0, -1.0,
 | 
				
			||||||
 | 
					        0.0,  0.0, -1.0,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        0.0,  1.0,  0.0,
 | 
				
			||||||
 | 
					        0.0,  1.0,  0.0,
 | 
				
			||||||
 | 
					        0.0,  1.0,  0.0,
 | 
				
			||||||
 | 
					        0.0,  1.0,  0.0,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        0.0, -1.0,  0.0,
 | 
				
			||||||
 | 
					        0.0, -1.0,  0.0,
 | 
				
			||||||
 | 
					        0.0, -1.0,  0.0,
 | 
				
			||||||
 | 
					        0.0, -1.0,  0.0,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        1.0,  0.0,  0.0,
 | 
				
			||||||
 | 
					        1.0,  0.0,  0.0,
 | 
				
			||||||
 | 
					        1.0,  0.0,  0.0,
 | 
				
			||||||
 | 
					        1.0,  0.0,  0.0,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        -1.0,  0.0,  0.0,
 | 
				
			||||||
 | 
					        -1.0,  0.0,  0.0,
 | 
				
			||||||
 | 
					        -1.0,  0.0,  0.0,
 | 
				
			||||||
 | 
					        -1.0,  0.0,  0.0
 | 
				
			||||||
 | 
					    ]),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export { Cube };
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,12 @@
 | 
				
			||||||
 | 
					#version 300 es
 | 
				
			||||||
 | 
					// FRAGMENT SHADER
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Basic fragment shader, just passes along colors, we don't do much
 | 
				
			||||||
 | 
					// with textures or anything else complex in this project.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					precision highp float;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					flat in vec4 vColor;
 | 
				
			||||||
 | 
					out vec4 color;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void main() { color = vColor; }
 | 
				
			||||||
							
								
								
									
										2
									
								
								templates/src/music/features/visualizer/shaders/fragments.glsl.d.ts
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								templates/src/music/features/visualizer/shaders/fragments.glsl.d.ts
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,2 @@
 | 
				
			||||||
 | 
					declare const fragments: string;
 | 
				
			||||||
 | 
					export default fragments;
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,50 @@
 | 
				
			||||||
 | 
					#version 300 es
 | 
				
			||||||
 | 
					// VERTEX SHADER
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Takes vertices of a unit cube, scales them up along Y according to
 | 
				
			||||||
 | 
					// aHeight, and colors them with basic diffuse shading.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define CLEAR_COLOR vec4(0.0588235294118, 0.0588235294118, 0.0588235294118, 1.0)
 | 
				
			||||||
 | 
					#define BASE_COLOR vec3(0.6, 0.819607843137, 0.807843137255)
 | 
				
			||||||
 | 
					#define AMBIENT_LIGHT vec3(0.3, 0.3, 0.3)
 | 
				
			||||||
 | 
					#define LIGHT_DIRECTION normalize(vec3(0.85, 0.8, 0.75))
 | 
				
			||||||
 | 
					#define LIGHT_COLOR vec3(1.0, 1.0, 1.0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					precision highp float;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					layout(location = 0) in vec4 aVertexPosition;
 | 
				
			||||||
 | 
					layout(location = 1) in vec3 aVertexNormal;
 | 
				
			||||||
 | 
					layout(location = 2) in float aHeight;
 | 
				
			||||||
 | 
					flat out vec4 vColor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					uniform mat4 uModelViewMatrix;
 | 
				
			||||||
 | 
					uniform mat4 uProjectionMatrix;
 | 
				
			||||||
 | 
					uniform mat4 uNormalMatrix;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void main() {
 | 
				
			||||||
 | 
					  // The X position of each vertex depends on its cube's instance;
 | 
				
			||||||
 | 
					  // they should align to the X axis.
 | 
				
			||||||
 | 
					  float instanceX =
 | 
				
			||||||
 | 
					      float(gl_InstanceID * 2) * abs(aVertexPosition.x) + aVertexPosition.x;
 | 
				
			||||||
 | 
					  // To scale the boxes by their frequencies, scale vertex Y by the
 | 
				
			||||||
 | 
					  // frequency.
 | 
				
			||||||
 | 
					  float vertexY = aVertexPosition.y * aHeight;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  gl_Position = uProjectionMatrix * uModelViewMatrix *
 | 
				
			||||||
 | 
					                vec4(instanceX, vertexY, aVertexPosition.zw);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (aHeight == 0.0) {
 | 
				
			||||||
 | 
					    // Don't render cubes that don't currently have a height
 | 
				
			||||||
 | 
					    // (frequency = 0)
 | 
				
			||||||
 | 
					    vColor = CLEAR_COLOR;
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    // Properly shade and color any other cubes
 | 
				
			||||||
 | 
					    vec4 transformedNormal = uNormalMatrix * vec4(aVertexNormal, 1.0);
 | 
				
			||||||
 | 
					    float directionalLight =
 | 
				
			||||||
 | 
					        max(dot(transformedNormal.xyz, LIGHT_DIRECTION), 0.0);
 | 
				
			||||||
 | 
					    vec3 appliedColor =
 | 
				
			||||||
 | 
					        BASE_COLOR * (directionalLight * LIGHT_COLOR + AMBIENT_LIGHT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    vColor = vec4(appliedColor.rgb, 1.0);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										2
									
								
								templates/src/music/features/visualizer/shaders/vertices.glsl.d.ts
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								templates/src/music/features/visualizer/shaders/vertices.glsl.d.ts
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,2 @@
 | 
				
			||||||
 | 
					declare const vertices: string;
 | 
				
			||||||
 | 
					export default vertices;
 | 
				
			||||||
							
								
								
									
										5
									
								
								templates/src/music/hooks.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								templates/src/music/hooks.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,5 @@
 | 
				
			||||||
 | 
					import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
 | 
				
			||||||
 | 
					import type { RootState, AppDispatch } from "./store";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const useAppDispatch: () => AppDispatch = useDispatch;
 | 
				
			||||||
 | 
					export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
 | 
				
			||||||
							
								
								
									
										31
									
								
								templates/src/music/index.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								templates/src/music/index.tsx
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,31 @@
 | 
				
			||||||
 | 
					import React from "react";
 | 
				
			||||||
 | 
					import { createRoot } from "react-dom/client";
 | 
				
			||||||
 | 
					import { Provider } from "react-redux";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import store from "./store";
 | 
				
			||||||
 | 
					import MusicPlayer from "./features/musicplayer/MusicPlayer";
 | 
				
			||||||
 | 
					import { setSource } from "./features/musicplayer/musicPlayerSlice";
 | 
				
			||||||
 | 
					import mseq from "./assets/Mseq_-_Journey.mp3";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const rootElement = document.getElementById("playerUI");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (rootElement === null) {
 | 
				
			||||||
 | 
					    throw Error("DOM seems to have failed to load. Something went very wrong.");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const root = createRoot(rootElement);
 | 
				
			||||||
 | 
					root.render(
 | 
				
			||||||
 | 
					    <Provider store={store}>
 | 
				
			||||||
 | 
					        <MusicPlayer />
 | 
				
			||||||
 | 
					    </Provider>
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					store.dispatch(
 | 
				
			||||||
 | 
					    setSource({
 | 
				
			||||||
 | 
					        source: mseq,
 | 
				
			||||||
 | 
					        artist: "Mseq",
 | 
				
			||||||
 | 
					        name: "Journey",
 | 
				
			||||||
 | 
					        album: "Unknown album",
 | 
				
			||||||
 | 
					        length: 192052244,
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
							
								
								
									
										18
									
								
								templates/src/music/music.scss
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								templates/src/music/music.scss
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,18 @@
 | 
				
			||||||
 | 
					$fa-font-path: "npm:@fortawesome/fontawesome-free/webfonts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@import "~/node_modules/@fortawesome/fontawesome-free/scss/fontawesome";
 | 
				
			||||||
 | 
					@import "~/node_modules/@fortawesome/fontawesome-free/scss/solid";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.is-border-box {
 | 
				
			||||||
 | 
					  box-sizing: border-box !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.is-absolute {
 | 
				
			||||||
 | 
					  position: absolute !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.is-bottom-left {
 | 
				
			||||||
 | 
					  bottom: 0;
 | 
				
			||||||
 | 
					  left: 0;
 | 
				
			||||||
 | 
					  position: absolute !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										15
									
								
								templates/src/music/player.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								templates/src/music/player.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,15 @@
 | 
				
			||||||
 | 
					class Player {
 | 
				
			||||||
 | 
					    constructor() {
 | 
				
			||||||
 | 
					        console.info("Test");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let player: Player | null = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default () => {
 | 
				
			||||||
 | 
					    if (player === null) {
 | 
				
			||||||
 | 
					        player = new Player();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return player;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										13
									
								
								templates/src/music/store.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								templates/src/music/store.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,13 @@
 | 
				
			||||||
 | 
					import { configureStore } from "@reduxjs/toolkit";
 | 
				
			||||||
 | 
					import musicPlayerReducer from "./features/musicplayer/musicPlayerSlice";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const store = configureStore({
 | 
				
			||||||
 | 
					    reducer: {
 | 
				
			||||||
 | 
					        musicPlayer: musicPlayerReducer,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type RootState = ReturnType<typeof store.getState>;
 | 
				
			||||||
 | 
					export type AppDispatch = typeof store.dispatch;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default store;
 | 
				
			||||||
							
								
								
									
										13
									
								
								templates/src/music_sample.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								templates/src/music_sample.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,13 @@
 | 
				
			||||||
 | 
					<extends src="./lib/html/base.html">
 | 
				
			||||||
 | 
					  <block name="stylesheets">
 | 
				
			||||||
 | 
					    <link rel="stylesheet" href="music/music.scss" />
 | 
				
			||||||
 | 
					  </block>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <block name="content">
 | 
				
			||||||
 | 
					    <div id="playerUI" class="is-flex-grow-1 is-flex"></div>
 | 
				
			||||||
 | 
					  </block>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <block name="footer" type="append">
 | 
				
			||||||
 | 
					    <script type="module" src="music/index.tsx"></script>
 | 
				
			||||||
 | 
					  </block>
 | 
				
			||||||
 | 
					</extends>
 | 
				
			||||||
							
								
								
									
										16
									
								
								templates/tsconfig.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								templates/tsconfig.json
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,16 @@
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "compilerOptions": {
 | 
				
			||||||
 | 
					    "strictNullChecks": true,
 | 
				
			||||||
 | 
					    "strictPropertyInitialization": true,
 | 
				
			||||||
 | 
					    "esModuleInterop": true,
 | 
				
			||||||
 | 
					    "jsx": "react",
 | 
				
			||||||
 | 
					    "isolatedModules": true,
 | 
				
			||||||
 | 
					    "target": "es2015",
 | 
				
			||||||
 | 
					    "moduleResolution": "node",
 | 
				
			||||||
 | 
					    "plugins": [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "name": "typescript-eslint-language-service"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue