replace sass with vanilla css

This commit is contained in:
Brent Schroeter 2025-12-18 12:55:01 -08:00
parent afba6497af
commit 4adbb84517
46 changed files with 1351 additions and 2000 deletions

1
.gitignore vendored
View file

@ -3,7 +3,6 @@ target
.env
.DS_Store
node_modules
css_dist
js_dist
pgdata
.vite

View file

@ -25,7 +25,6 @@ USER 1000
WORKDIR /home/app
COPY --from=builder /app/target/release/phono-server /usr/local/bin
COPY ./css_dist ./css_dist
COPY ./js_dist ./js_dist
COPY ./static ./static

252
deno.lock generated
View file

@ -14,7 +14,6 @@
"npm:@sveltejs/vite-plugin-svelte@^6.1.1": "6.1.1_svelte@5.38.1__acorn@8.15.0_vite@7.1.2__picomatch@4.0.3_sass-embedded@1.91.0",
"npm:@tsconfig/svelte@^5.0.4": "5.0.4",
"npm:date-fns@^4.1.0": "4.1.0",
"npm:sass-embedded@^1.91.0": "1.91.0",
"npm:svelte-check@^4.3.1": "4.3.1_svelte@5.38.1__acorn@8.15.0_typescript@5.8.3",
"npm:svelte-language-server@~0.17.19": "0.17.19_prettier@3.3.3_svelte@4.2.20_typescript@5.9.2",
"npm:svelte@^5.37.3": "5.38.1_acorn@8.15.0",
@ -70,16 +69,10 @@
"@date-fns/utc@2.1.1": {
"integrity": "sha512-SlJDfG6RPeEX8wEVv6ZB3kak4MmbtyiI2qX/5zuKdordbrhB/iaJ58GVMZgJ6P1sJaM1gMgENFYYeg1JWrCFrA=="
},
"@deno/vite-plugin@1.0.5_vite@7.1.2__picomatch@4.0.3": {
"integrity": "sha512-tLja5n4dyMhcze1NzvSs2iiriBymfBlDCZIrjMTxb9O2ru0gvmV6mn5oBD2teNw5Sd92cj3YJzKwsAs8tMJXlg==",
"dependencies": [
"vite@7.1.2_picomatch@4.0.3"
]
},
"@deno/vite-plugin@1.0.5_vite@7.1.2__picomatch@4.0.3_sass-embedded@1.91.0": {
"integrity": "sha512-tLja5n4dyMhcze1NzvSs2iiriBymfBlDCZIrjMTxb9O2ru0gvmV6mn5oBD2teNw5Sd92cj3YJzKwsAs8tMJXlg==",
"dependencies": [
"vite@7.1.2_picomatch@4.0.3_sass-embedded@1.91.0"
"vite"
]
},
"@emmetio/abbreviation@2.3.3": {
@ -97,261 +90,131 @@
"@emmetio/scanner@1.0.4": {
"integrity": "sha512-IqRuJtQff7YHHBk4G8YZ45uB9BaAGcwQeVzgj/zj8/UdOhtQpEIupUhSk8dys6spFIWVZVeK20CzGEnqR5SbqA=="
},
"@esbuild/aix-ppc64@0.25.8": {
"integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==",
"os": ["aix"],
"cpu": ["ppc64"]
},
"@esbuild/aix-ppc64@0.25.9": {
"integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==",
"os": ["aix"],
"cpu": ["ppc64"]
},
"@esbuild/android-arm64@0.25.8": {
"integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==",
"os": ["android"],
"cpu": ["arm64"]
},
"@esbuild/android-arm64@0.25.9": {
"integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==",
"os": ["android"],
"cpu": ["arm64"]
},
"@esbuild/android-arm@0.25.8": {
"integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==",
"os": ["android"],
"cpu": ["arm"]
},
"@esbuild/android-arm@0.25.9": {
"integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==",
"os": ["android"],
"cpu": ["arm"]
},
"@esbuild/android-x64@0.25.8": {
"integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==",
"os": ["android"],
"cpu": ["x64"]
},
"@esbuild/android-x64@0.25.9": {
"integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==",
"os": ["android"],
"cpu": ["x64"]
},
"@esbuild/darwin-arm64@0.25.8": {
"integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==",
"os": ["darwin"],
"cpu": ["arm64"]
},
"@esbuild/darwin-arm64@0.25.9": {
"integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==",
"os": ["darwin"],
"cpu": ["arm64"]
},
"@esbuild/darwin-x64@0.25.8": {
"integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==",
"os": ["darwin"],
"cpu": ["x64"]
},
"@esbuild/darwin-x64@0.25.9": {
"integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==",
"os": ["darwin"],
"cpu": ["x64"]
},
"@esbuild/freebsd-arm64@0.25.8": {
"integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==",
"os": ["freebsd"],
"cpu": ["arm64"]
},
"@esbuild/freebsd-arm64@0.25.9": {
"integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==",
"os": ["freebsd"],
"cpu": ["arm64"]
},
"@esbuild/freebsd-x64@0.25.8": {
"integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==",
"os": ["freebsd"],
"cpu": ["x64"]
},
"@esbuild/freebsd-x64@0.25.9": {
"integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==",
"os": ["freebsd"],
"cpu": ["x64"]
},
"@esbuild/linux-arm64@0.25.8": {
"integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==",
"os": ["linux"],
"cpu": ["arm64"]
},
"@esbuild/linux-arm64@0.25.9": {
"integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==",
"os": ["linux"],
"cpu": ["arm64"]
},
"@esbuild/linux-arm@0.25.8": {
"integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==",
"os": ["linux"],
"cpu": ["arm"]
},
"@esbuild/linux-arm@0.25.9": {
"integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==",
"os": ["linux"],
"cpu": ["arm"]
},
"@esbuild/linux-ia32@0.25.8": {
"integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==",
"os": ["linux"],
"cpu": ["ia32"]
},
"@esbuild/linux-ia32@0.25.9": {
"integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==",
"os": ["linux"],
"cpu": ["ia32"]
},
"@esbuild/linux-loong64@0.25.8": {
"integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==",
"os": ["linux"],
"cpu": ["loong64"]
},
"@esbuild/linux-loong64@0.25.9": {
"integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==",
"os": ["linux"],
"cpu": ["loong64"]
},
"@esbuild/linux-mips64el@0.25.8": {
"integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==",
"os": ["linux"],
"cpu": ["mips64el"]
},
"@esbuild/linux-mips64el@0.25.9": {
"integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==",
"os": ["linux"],
"cpu": ["mips64el"]
},
"@esbuild/linux-ppc64@0.25.8": {
"integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==",
"os": ["linux"],
"cpu": ["ppc64"]
},
"@esbuild/linux-ppc64@0.25.9": {
"integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==",
"os": ["linux"],
"cpu": ["ppc64"]
},
"@esbuild/linux-riscv64@0.25.8": {
"integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==",
"os": ["linux"],
"cpu": ["riscv64"]
},
"@esbuild/linux-riscv64@0.25.9": {
"integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==",
"os": ["linux"],
"cpu": ["riscv64"]
},
"@esbuild/linux-s390x@0.25.8": {
"integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==",
"os": ["linux"],
"cpu": ["s390x"]
},
"@esbuild/linux-s390x@0.25.9": {
"integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==",
"os": ["linux"],
"cpu": ["s390x"]
},
"@esbuild/linux-x64@0.25.8": {
"integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==",
"os": ["linux"],
"cpu": ["x64"]
},
"@esbuild/linux-x64@0.25.9": {
"integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==",
"os": ["linux"],
"cpu": ["x64"]
},
"@esbuild/netbsd-arm64@0.25.8": {
"integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==",
"os": ["netbsd"],
"cpu": ["arm64"]
},
"@esbuild/netbsd-arm64@0.25.9": {
"integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==",
"os": ["netbsd"],
"cpu": ["arm64"]
},
"@esbuild/netbsd-x64@0.25.8": {
"integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==",
"os": ["netbsd"],
"cpu": ["x64"]
},
"@esbuild/netbsd-x64@0.25.9": {
"integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==",
"os": ["netbsd"],
"cpu": ["x64"]
},
"@esbuild/openbsd-arm64@0.25.8": {
"integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==",
"os": ["openbsd"],
"cpu": ["arm64"]
},
"@esbuild/openbsd-arm64@0.25.9": {
"integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==",
"os": ["openbsd"],
"cpu": ["arm64"]
},
"@esbuild/openbsd-x64@0.25.8": {
"integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==",
"os": ["openbsd"],
"cpu": ["x64"]
},
"@esbuild/openbsd-x64@0.25.9": {
"integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==",
"os": ["openbsd"],
"cpu": ["x64"]
},
"@esbuild/openharmony-arm64@0.25.8": {
"integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==",
"os": ["openharmony"],
"cpu": ["arm64"]
},
"@esbuild/openharmony-arm64@0.25.9": {
"integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==",
"os": ["openharmony"],
"cpu": ["arm64"]
},
"@esbuild/sunos-x64@0.25.8": {
"integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==",
"os": ["sunos"],
"cpu": ["x64"]
},
"@esbuild/sunos-x64@0.25.9": {
"integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==",
"os": ["sunos"],
"cpu": ["x64"]
},
"@esbuild/win32-arm64@0.25.8": {
"integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==",
"os": ["win32"],
"cpu": ["arm64"]
},
"@esbuild/win32-arm64@0.25.9": {
"integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==",
"os": ["win32"],
"cpu": ["arm64"]
},
"@esbuild/win32-ia32@0.25.8": {
"integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==",
"os": ["win32"],
"cpu": ["ia32"]
},
"@esbuild/win32-ia32@0.25.9": {
"integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==",
"os": ["win32"],
"cpu": ["ia32"]
},
"@esbuild/win32-x64@0.25.8": {
"integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==",
"os": ["win32"],
"cpu": ["x64"]
},
"@esbuild/win32-x64@0.25.9": {
"integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==",
"os": ["win32"],
@ -580,48 +443,26 @@
"acorn"
]
},
"@sveltejs/vite-plugin-svelte-inspector@5.0.0_@sveltejs+vite-plugin-svelte@6.1.1__svelte@5.38.1___acorn@8.15.0__vite@7.1.2___picomatch@4.0.3_svelte@5.38.1__acorn@8.15.0_vite@7.1.2__picomatch@4.0.3": {
"integrity": "sha512-iwQ8Z4ET6ZFSt/gC+tVfcsSBHwsqc6RumSaiLUkAurW3BCpJam65cmHw0oOlDMTO0u+PZi9hilBRYN+LZNHTUQ==",
"dependencies": [
"@sveltejs/vite-plugin-svelte@6.1.1_svelte@5.38.1__acorn@8.15.0_vite@7.1.2__picomatch@4.0.3",
"debug",
"svelte@5.38.1_acorn@8.15.0",
"vite@7.1.2_picomatch@4.0.3"
]
},
"@sveltejs/vite-plugin-svelte-inspector@5.0.0_@sveltejs+vite-plugin-svelte@6.1.1__svelte@5.38.1___acorn@8.15.0__vite@7.1.2___picomatch@4.0.3_svelte@5.38.1__acorn@8.15.0_vite@7.1.2__picomatch@4.0.3_sass-embedded@1.91.0": {
"integrity": "sha512-iwQ8Z4ET6ZFSt/gC+tVfcsSBHwsqc6RumSaiLUkAurW3BCpJam65cmHw0oOlDMTO0u+PZi9hilBRYN+LZNHTUQ==",
"dependencies": [
"@sveltejs/vite-plugin-svelte@6.1.1_svelte@5.38.1__acorn@8.15.0_vite@7.1.2__picomatch@4.0.3_sass-embedded@1.91.0",
"@sveltejs/vite-plugin-svelte",
"debug",
"svelte@5.38.1_acorn@8.15.0",
"vite@7.1.2_picomatch@4.0.3_sass-embedded@1.91.0"
]
},
"@sveltejs/vite-plugin-svelte@6.1.1_svelte@5.38.1__acorn@8.15.0_vite@7.1.2__picomatch@4.0.3": {
"integrity": "sha512-vB0Vq47Js7C11L2JrwhncIAoDNkdKDPI500SjLSb34X48dDcsSH5JpLl0cHT0sfO997BrzAS6PKjiZEey/S0VQ==",
"dependencies": [
"@sveltejs/vite-plugin-svelte-inspector@5.0.0_@sveltejs+vite-plugin-svelte@6.1.1__svelte@5.38.1___acorn@8.15.0__vite@7.1.2___picomatch@4.0.3_svelte@5.38.1__acorn@8.15.0_vite@7.1.2__picomatch@4.0.3",
"debug",
"deepmerge",
"kleur",
"magic-string",
"svelte@5.38.1_acorn@8.15.0",
"vite@7.1.2_picomatch@4.0.3",
"vitefu@1.1.1_vite@7.1.2__picomatch@4.0.3"
"vite"
]
},
"@sveltejs/vite-plugin-svelte@6.1.1_svelte@5.38.1__acorn@8.15.0_vite@7.1.2__picomatch@4.0.3_sass-embedded@1.91.0": {
"integrity": "sha512-vB0Vq47Js7C11L2JrwhncIAoDNkdKDPI500SjLSb34X48dDcsSH5JpLl0cHT0sfO997BrzAS6PKjiZEey/S0VQ==",
"dependencies": [
"@sveltejs/vite-plugin-svelte-inspector@5.0.0_@sveltejs+vite-plugin-svelte@6.1.1__svelte@5.38.1___acorn@8.15.0__vite@7.1.2___picomatch@4.0.3_svelte@5.38.1__acorn@8.15.0_vite@7.1.2__picomatch@4.0.3_sass-embedded@1.91.0",
"@sveltejs/vite-plugin-svelte-inspector",
"debug",
"deepmerge",
"kleur",
"magic-string",
"svelte@5.38.1_acorn@8.15.0",
"vite@7.1.2_picomatch@4.0.3_sass-embedded@1.91.0",
"vitefu@1.1.1_vite@7.1.2__picomatch@4.0.3_sass-embedded@1.91.0"
"vite",
"vitefu"
]
},
"@tsconfig/svelte@5.0.4": {
@ -721,32 +562,32 @@
"esbuild@0.25.9": {
"integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==",
"optionalDependencies": [
"@esbuild/aix-ppc64@0.25.9",
"@esbuild/android-arm@0.25.9",
"@esbuild/android-arm64@0.25.9",
"@esbuild/android-x64@0.25.9",
"@esbuild/darwin-arm64@0.25.9",
"@esbuild/darwin-x64@0.25.9",
"@esbuild/freebsd-arm64@0.25.9",
"@esbuild/freebsd-x64@0.25.9",
"@esbuild/linux-arm@0.25.9",
"@esbuild/linux-arm64@0.25.9",
"@esbuild/linux-ia32@0.25.9",
"@esbuild/linux-loong64@0.25.9",
"@esbuild/linux-mips64el@0.25.9",
"@esbuild/linux-ppc64@0.25.9",
"@esbuild/linux-riscv64@0.25.9",
"@esbuild/linux-s390x@0.25.9",
"@esbuild/linux-x64@0.25.9",
"@esbuild/netbsd-arm64@0.25.9",
"@esbuild/netbsd-x64@0.25.9",
"@esbuild/openbsd-arm64@0.25.9",
"@esbuild/openbsd-x64@0.25.9",
"@esbuild/openharmony-arm64@0.25.9",
"@esbuild/sunos-x64@0.25.9",
"@esbuild/win32-arm64@0.25.9",
"@esbuild/win32-ia32@0.25.9",
"@esbuild/win32-x64@0.25.9"
"@esbuild/aix-ppc64",
"@esbuild/android-arm",
"@esbuild/android-arm64",
"@esbuild/android-x64",
"@esbuild/darwin-arm64",
"@esbuild/darwin-x64",
"@esbuild/freebsd-arm64",
"@esbuild/freebsd-x64",
"@esbuild/linux-arm",
"@esbuild/linux-arm64",
"@esbuild/linux-ia32",
"@esbuild/linux-loong64",
"@esbuild/linux-mips64el",
"@esbuild/linux-ppc64",
"@esbuild/linux-riscv64",
"@esbuild/linux-s390x",
"@esbuild/linux-x64",
"@esbuild/netbsd-arm64",
"@esbuild/netbsd-x64",
"@esbuild/openbsd-arm64",
"@esbuild/openbsd-x64",
"@esbuild/openharmony-arm64",
"@esbuild/sunos-x64",
"@esbuild/win32-arm64",
"@esbuild/win32-ia32",
"@esbuild/win32-x64"
],
"scripts": true,
"bin": true
@ -1242,21 +1083,6 @@
"varint@6.0.0": {
"integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg=="
},
"vite@7.1.2_picomatch@4.0.3": {
"integrity": "sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==",
"dependencies": [
"esbuild",
"fdir",
"picomatch@4.0.3",
"postcss",
"rollup",
"tinyglobby"
],
"optionalDependencies": [
"fsevents"
],
"bin": true
},
"vite@7.1.2_picomatch@4.0.3_sass-embedded@1.91.0": {
"integrity": "sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==",
"dependencies": [
@ -1276,22 +1102,13 @@
],
"bin": true
},
"vitefu@1.1.1_vite@7.1.2__picomatch@4.0.3": {
"integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==",
"dependencies": [
"vite@7.1.2_picomatch@4.0.3"
],
"optionalPeers": [
"vite@7.1.2_picomatch@4.0.3"
]
},
"vitefu@1.1.1_vite@7.1.2__picomatch@4.0.3_sass-embedded@1.91.0": {
"integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==",
"dependencies": [
"vite@7.1.2_picomatch@4.0.3_sass-embedded@1.91.0"
"vite"
],
"optionalPeers": [
"vite@7.1.2_picomatch@4.0.3_sass-embedded@1.91.0"
"vite"
]
},
"vscode-css-languageservice@6.3.7": {
@ -1364,7 +1181,6 @@
"npm:@sveltejs/vite-plugin-svelte@^6.1.1",
"npm:@tsconfig/svelte@^5.0.4",
"npm:date-fns@^4.1.0",
"npm:sass-embedded@^1.91.0",
"npm:svelte-check@^4.3.1",
"npm:svelte-language-server@~0.17.19",
"npm:svelte@^5.37.3",

View file

@ -3,7 +3,6 @@ deno = "latest"
rebar = "latest"
rust = { version = "nightly", components = "rust-analyzer,clippy,rustc-codegen-cranelift-preview" }
watchexec = "latest"
"github:sass/dart-sass" = "1.89.2"
"cargo:sqlx-cli" = "0.8.6"
[tasks.dev-services]
@ -20,10 +19,6 @@ run = "deno run -A npm:vite build"
dir = "./svelte"
sources = ["**/*.svelte.ts", "**/*.svelte"]
[tasks.build-css]
run = "sass sass/:css_dist/"
sources = ["**/*.scss"]
[tasks.docker-services]
dir = "./dev-services"
run = "docker compose up"

View file

@ -15,7 +15,6 @@
"@deno/vite-plugin": "^1.0.5",
"@sveltejs/vite-plugin-svelte": "^6.1.1",
"date-fns": "^4.1.0",
"sass-embedded": "^1.91.0",
"svelte-language-server": "^0.17.19",
"uuid": "^11.1.0",
"vite": "^7.1.1",

View file

@ -71,31 +71,6 @@ pub(crate) fn new_router(app: App) -> Router<()> {
),
),
)
.nest_service(
"/css_dist",
ServiceBuilder::new()
.layer(SetResponseHeaderLayer::if_not_present(
CACHE_CONTROL,
HeaderValue::from_static(if cfg!(debug_assertions) {
// Disable caching when developing locally.
"no-cache"
} else {
"max-age=120, stale-while-revalidate=86400"
}),
))
.service(
ServeDir::new("css_dist").not_found_service(
ServiceBuilder::new()
.layer(SetResponseHeaderLayer::if_not_present(
CACHE_CONTROL,
// Do not allow caching of paths if they return
// a 404 error.
HeaderValue::from_static("no-cache"),
))
.service(ServeFile::new("static/_404.html")),
),
),
)
.fallback_service(
ServiceBuilder::new()
.layer(SetResponseHeaderLayer::if_not_present(

View file

@ -3,7 +3,9 @@
<head>
<title>{% block title %}Phonograph{% endblock %}</title>
{% include "meta_tags.html" %}
<link rel="stylesheet" href="{{ settings.root_path }}/css_dist/main.css">
<link rel="stylesheet" href="{{ settings.root_path }}/modern-normalize.min.css">
<link rel="stylesheet" href="{{ settings.root_path }}/main.css">
<link rel="stylesheet" href="{{ settings.root_path }}/tabler-icons/webfont/tabler-icons.min.css">
<script type="module" src="{{ settings.root_path }}/js_dist/basic-dropdown.webc.mjs"></script>
{%- block head_extras %}{% endblock -%}
</head>

View file

@ -1,7 +1,7 @@
{% extends "base.html" %}
{% block head_extras %}
<link rel="stylesheet" href="{{ settings.root_path }}/css_dist/viewer.css">
<link rel="stylesheet" href="{{ settings.root_path }}/portal-table.css">
<script type="module" src="{{ settings.root_path }}/js_dist/table-viewer.webc.mjs"></script>
<script type="module" src="{{ settings.root_path }}/js_dist/filter-menu.webc.mjs"></script>
{% endblock %}
@ -10,7 +10,7 @@
<div class="page-grid">
<div class="page-grid__toolbar">
<div class="page-grid__toolbar-utilities">
<a class="button--secondary" href="settings" role="button">
<a class="button button--secondary" href="settings" role="button">
Portal Settings
</a>
<filter-menu

View file

@ -1,13 +1,18 @@
<div class="role-display">
<div class="role-display__resource">{{ self.relname }}</div>
<div class="role-display__description">
{%- match self.kind -%}
<div class="permission-badge">
<div class="sr-only">{{ self.kind }} of {{ self.relname }}</div>
<div aria-hidden="true" class="permission-badge__resource">{{ self.relname }}</div>
<div
aria-hidden="true"
class="permission-badge__description"
style="--permission-badge-description-color: {% match self.kind -%}
{%- when RelPermissionKind::Owner -%}
owner
#fca
{%- when RelPermissionKind::Reader -%}
reader
#acf
{%- when RelPermissionKind::Writer -%}
writer
{%- endmatch -%}
#fac
{%- endmatch %};"
>
{{ self.kind }}
</div>
</div>

View file

@ -5,7 +5,7 @@
<div class="page-grid__toolbar">
<div class="page-grid__toolbar-utilities">
<a
class="button--secondary"
class="button button--secondary"
href="{{ navigator.portal_page()
.workspace_id(*portal.workspace_id)
.rel_oid(*portal.class_oid)
@ -24,12 +24,24 @@
{{ workspace_nav | safe }}
</div>
</div>
<main class="page-grid__main padded--lg">
<main class="page-grid__main padded padded--lg">
<form method="post" action="update-name">
<section>
<h1>Portal Name</h1>
<input type="text" name="name" value="{{ portal.name }}">
<button class="button--primary" type="submit">Save</button>
<input
type="text"
autocomplete="off"
class="form__input"
data-1p-ignore
data-bwignore="true"
data-lpignore="true"
data-protonpass-ignore="true"
name="name"
value="{{ portal.name }}"
>
<div class="form__buttons">
<button class="button button--primary" type="submit">Save</button>
</div>
</section>
</form>
</main>

View file

@ -10,18 +10,24 @@
{{ workspace_nav | safe }}
</div>
</div>
<main class="page-grid__main padded--lg">
<main class="page-grid__main padded padded--lg">
<form method="post" action="update-name">
<section>
<h1>Table Name</h1>
<input type="text" name="name" value="{{ rel.relname }}">
<button class="button--primary" type="submit">Save</button>
</section>
</form>
<form method="post" action="">
<section>
<h1>Sharing</h1>
<button class="button--primary" type="submit">Save</button>
<input
type="text"
autocomplete="off"
class="form__input"
data-1p-ignore
data-bwignore="true"
data-lpignore="true"
data-protonpass-ignore="true"
name="name"
value="{{ rel.relname }}"
>
<div class="form__buttons">
<button class="button button--primary" type="submit">Save</button>
</div>
</section>
</form>
</main>

View file

@ -47,28 +47,23 @@
method="post"
>
<!-- FIXME: CSRF -->
<button class="button--secondary button--small" type="submit">
<i class="ti ti-database-plus"><div class="sr-only">Add table</div></i>
<button class="button button--secondary button--small" type="submit">
<div class="sr-only">Add table</div>
<i class="ti ti-database-plus"></i>
</button>
</form>
</div>
<menu class="workspace-nav__menu">
{%- for rel in relations %}
<li>
<collapsible-menu
class="workspace-nav__menu-item"
expanded="
{%- if let Some(NavLocation::Rel(rel_oid, _)) = current -%}
{%- if rel_oid == &rel.oid -%}
true
{%- endif -%}
{%- endif -%}
"
>
<div class="workspace-nav__heading" slot="summary">
<h3>{{ rel.name }}</h3>
<basic-dropdown button-class="button--secondary button--small" button-aria-label="Table menu">
<span slot="button-contents"><i class="ti ti-dots-vertical" aria-hidden="true"></i></span>
<div class="workspace-nav__menu-item">
<div class="workspace-nav__heading">
<h3 class="text--data">{{ rel.name }}</h3>
<basic-dropdown>
<span slot="button-contents">
<span class="sr-only">Table menu</span>
<i class="ti ti-dots-vertical" aria-hidden="true"></i>
</span>
<menu slot="popover" class="basic-dropdown__menu">
<li>
<a
@ -84,10 +79,9 @@
</menu>
</basic-dropdown>
</div>
<menu class="workspace-nav__menu" slot="content">
<menu class="workspace-nav__menu">
<li class="workspace-nav__menu-item">
<collapsible-menu class="workspace-nav__collapsible-menu">
<div slot="summary" class="workspace-nav__heading">
<div class="workspace-nav__heading">
<h4>Portals</h4>
<form
action="{{ navigator.get_root_path() -}}
@ -97,7 +91,11 @@
method="post"
>
<!-- FIXME: CSRF -->
<button aria-label="Add portal" class="workspace-nav__aux-button" type="submit">
<button
class="button button--secondary button--small"
type="submit"
>
<div class="sr-only">Add portal</div>
<i class="ti ti-table-plus"></i>
</button>
</form>
@ -117,7 +115,7 @@
/r/{{ rel.oid.0 -}}
/p/{{ portal.id.simple() -}}
"
class="workspace-nav__menu-link"
class="workspace-nav__menu-link text--data"
>
{{ portal.name }}
</a>
@ -136,28 +134,15 @@
Portal settings
</a>
</li>
<li>
<a
href="{{ navigator.get_root_path() -}}
/w/{{ workspace.id.simple() -}}
/r/{{ rel.oid.0 -}}
/p/{{ portal.id.simple() -}}
/form/"
role="button"
>
Form
</a>
</li>
</menu>
</basic-dropdown>
</div>
</li>
{% endfor %}
</menu>
</collapsible-menu>
</li>
</menu>
</collapsible-menu>
</div>
</li>
{% endfor -%}
</menu>

View file

@ -1,3 +1,4 @@
<div class="permissions-list">
{% for perm in current_perms.clone() %}
{{ perm | safe }}
{% endfor %}
@ -9,8 +10,9 @@
>
Edit
</button>
</div>
<dialog
class="dialog padded--lg"
class="dialog padded padded--lg"
id="permissions-editor-{{ target }}"
popover="auto"
>
@ -82,7 +84,7 @@
<input type="hidden" name="{{ k }}" value="{{ v }}">
{% endfor %}
<button
class="button--primary"
class="button button--primary"
style="margin-top: 16px;"
type="submit"
>

View file

@ -24,7 +24,7 @@
{{ workspace_nav | safe }}
</div>
</div>
<main class="page-grid__main padded--lg">
<main class="page-grid__main padded padded--lg">
<table class="table">
<thead>
<tr>

View file

@ -10,21 +10,35 @@
{{ workspace_nav | safe }}
</div>
</div>
<main class="page-grid__main padded--lg">
<main class="page-grid__main padded padded--lg">
<form method="post" action="update-name">
<section>
<h1>Workspace Name</h1>
<input type="text" name="name" value="{{ workspace.display_name }}">
<button class="button--primary" type="submit">Save</button>
<input
type="text"
autocomplete="off"
class="form__input"
data-1p-ignore
data-bwignore="true"
data-lpignore="true"
data-protonpass-ignore="true"
name="name"
value="{{ workspace.display_name }}"
>
<div class="form__buttons">
<button class="button button--primary" type="submit">Save</button>
</div>
</section>
</form>
<section>
<h1>Sharing</h1>
<div class="form__buttons">
<button class="button button--primary" popovertarget="invite-dialog" type="button">
Invite
</button>
</div>
<dialog
class="dialog padded--lg"
class="dialog padded padded--lg"
id="invite-dialog"
popover="auto"
>
@ -51,7 +65,7 @@
</div>
</form>
</dialog>
<table class="table">
<table class="table" style="margin-top: var(--default-padding);">
<thead>
<tr>
<th scope="col">Email</th>
@ -63,7 +77,7 @@
{% for permissions_editor in collaborators %}
<tr>
{% let collaborator = User::try_from(permissions_editor.target.clone())? %}
<td>{{ collaborator.email }}</td>
<td class="text--data">{{ collaborator.email }}</td>
<td>
{{ permissions_editor | safe }}
</td>
@ -80,7 +94,9 @@
{% endfor %}
</tbody>
</table>
<button class="button--primary" type="submit">Save</button>
<div class="form__buttons">
<button class="button button--primary" type="submit">Save</button>
</div>
</section>
</main>
</div>

View file

@ -1,40 +0,0 @@
@use 'globals';
.basic-dropdown {
&__button {
anchor-name: --anchor-button;
}
&__popover {
&:popover-open {
@include globals.popover;
padding: 0;
position: absolute;
position-anchor: --anchor-button;
top: anchor(bottom);
}
}
&__menu {
list-style-type: none;
margin: 0;
padding: 8px 0;
& > li {
padding: 0;
&:hover {
background: #0001;
}
& > button, & > [role="button"] {
@include globals.reset-button;
@include globals.reset-anchor;
display: block;
padding: 8px 16px;
}
}
}
}

View file

@ -1,30 +0,0 @@
@use 'globals';
@use 'viewer-shared';
.field-adder {
&__container {
align-items: stretch;
display: flex;
}
&__header-lookalike {
@include viewer-shared.th;
border-bottom-style: dashed;
border-right-style: dashed;
}
&__label-input {
@include globals.reset-input;
}
&__popover:popover-open {
@include globals.popover;
padding: 1rem;
}
&__summary-buttons {
align-items: center;
display: flex;
}
}

View file

@ -1,57 +0,0 @@
@use 'globals';
$section-gap: 1.5rem;
$label-gap: 0.5rem;
$button-gap: 0.25rem;
.form-section {
&__heading {
margin: 0;
font-size: 1rem;
font-weight: 700;
}
&__label {
display: block;
font-weight: 600;
margin-top: $section-gap;
}
&__input {
display: block;
margin-top: $label-gap;
font-family: globals.$font-family-data;
&--text {
@include globals.rounded;
border: globals.$default-border;
padding: 0.5rem;
}
}
}
.form-buttons {
display: flex;
margin-top: $section-gap;
justify-content: flex-end;
&__button {
margin: 0 $button-gap;
&:first-child {
margin-left: 0;
}
&:last-child {
margin-right: 0;
}
&--cancel {
@include globals.button-clear;
}
&--submit {
@include globals.button-primary;
}
}
}

View file

@ -1,153 +0,0 @@
@use 'sass:color';
$button-primary-background: #fc0;
$button-primary-color: #000;
$button-shadow: 0 0.15rem 0.15rem #3331;
$default-border-color: #ccc;
$default-border: solid 1px $default-border-color;
$font-family-default: 'Funnel Sans', 'Open Sans', 'Helvetica Neue', Arial, sans-serif;
$font-family-data: Menlo, 'Courier New', 'Open Sans', 'Helvetica Neue', Arial, sans-serif;
$font-family-mono: Menlo, 'Courier New', Courier, mono;
$popover-border: $default-border;
$popover-shadow: 0 0.5rem 0.5rem #3333;
$border-radius-rounded-sm: 0.25rem;
$border-radius-rounded: 0.5rem;
$link-color: #069;
$notice-color-info: #39d;
$hover-lightness-scale-factor: -5%;
@mixin reset-button {
appearance: none;
background: none;
border: none;
padding: 0;
box-sizing: border-box;
cursor: pointer;
font-family: inherit;
font-size: inherit;
font-weight: inherit;
}
@mixin button-base {
@include reset-button;
@include rounded;
box-shadow: $button-shadow;
font-family: $font-family-mono;
font-weight: 500;
padding: 0.5rem 1rem;
transition: background 0.2s ease;
}
@mixin button-primary {
@include button-base;
background: $button-primary-background;
border: solid 1px color.scale(
$button-primary-background,
$lightness: -5%,
$space: oklch
);
color: $button-primary-color;
&:hover {
background: color.scale(
$button-primary-background,
$lightness: $hover-lightness-scale-factor,
$space: oklch
);
border-color: color.scale(
$button-primary-background,
$lightness: -10%,
$space: oklch
);
}
}
@mixin button-outline {
@include button-base;
background: $button-primary-color;
border: solid 1px color.scale(
$button-primary-background,
$lightness: -5%,
$space: oklch
);
color: $button-primary-background;
&:hover {
border-color: color.scale(
$button-primary-background,
$lightness: -10%,
$space: oklch
);
}
}
@mixin button-secondary {
@include button-base;
background: #fff;
color: #000;
border: $default-border;
&:hover {
border-color: color.scale(
$default-border-color,
$lightness: $hover-lightness-scale-factor,
$space: oklch
);
}
}
@mixin button-clear {
@include button-base;
box-shadow: none;
&:hover {
background: #0000001f;
}
}
@mixin button-small {
padding: 4px 8px;
font-size: 0.9rem;
}
@mixin reset-input {
appearance: none;
background: none;
border: none;
box-sizing: border-box;
font-family: inherit;
font-size: inherit;
font-weight: inherit;
outline: none;
}
@mixin reset-anchor {
color: inherit;
text-decoration: none;
}
@mixin rounded-sm {
border-radius: $border-radius-rounded-sm;
}
@mixin rounded {
border-radius: $border-radius-rounded;
}
@mixin popover {
@include rounded;
inset: unset;
border: $popover-border;
margin: 0;
margin-top: 4px;
padding: 0;
position: relative;
display: block;
background: #fff;
box-shadow: $popover-shadow;
}

View file

@ -1,121 +0,0 @@
@use 'globals';
$background-color: #fff;
$tab-button-color-active: #0001;
.container-positioner {
position: fixed;
bottom: 2rem;
display: flex;
justify-content: center;
align-items: flex-end;
overflow: visible;
width: 100%;
height: 0;
}
.container {
display: grid;
grid-template-columns: max-content max-content 1rem max-content;
filter: drop-shadow(globals.$popover-shadow);
}
.tab-box {
@include globals.rounded;
height: 3rem;
display: flex;
align-items: center;
list-style-type: none;
padding: 0.5rem;
margin: 0;
border: globals.$popover-border;
border-right: none;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
background: $background-color;
grid-row: 2;
&__button {
@include globals.reset-button;
@include globals.rounded-sm;
display: flex;
align-items: center;
padding: 0.25rem 0.5rem;
cursor: pointer;
height: 2rem;
&--active {
background: $tab-button-color-active;
}
}
}
.control-bar {
@include globals.rounded;
height: 3rem;
flex-shrink: 0;
border: globals.$popover-border;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
overflow: hidden;
width: 40rem;
grid-row: 2;
background: $background-color;
&--open {
border-top-right-radius: 0;
}
}
.control-buttons {
height: 3rem;
gird-row: 2;
grid-column: 4;
height: 100%;
}
.control-panel-positioner {
grid-template-columns: subgrid;
grid-column: 2;
grid-row: 1;
position: relative;
overflow: visible;
/* Flexbox positioning is required for Safari */
display: flex;
align-items: flex-end;
anchor-name: --control-bar;
}
.control-panel__container:popover-open {
@include globals.rounded;
inset: unset;
border: globals.$popover-border;
border-bottom: none;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
margin: 0;
position: fixed;
display: block;
width: 40rem;
/* Anchor positioning is required for Chromium */
position-anchor: --control-bar;
position-area: top;
padding: 0;
background: $background-color;
box-shadow: globals.$popover-shadow;
/* Clip drop shadow */
clip-path: polygon(
-100% -100%,
200% -100%,
200% 200%,
100% 200%,
100% 100%,
-100% 100%
);
}
.control-panel {
padding: 0.5rem;
overflow: auto;
max-height: 8rem;
}

View file

@ -1,16 +0,0 @@
@use 'globals';
@mixin th {
border: globals.$default-border;
border-top: none;
font-family: 'Funnel Sans';
font-weight: bolder;
background: #0001;
height: 100%; /* css hack to make percentage based cell heights work */
padding: 0.25rem 0.5rem;
text-align: left;
&:first-child {
border-left: none;
}
}

View file

@ -1,34 +0,0 @@
@use 'globals';
.cell__container {
display: block;
width: 100%;
height: 100%;
border: solid 2px transparent;
&--selected {
border-color: #07f;
}
}
.cell__content {
font-family: globals.$font-family-data;
max-width: 100%;
&--padded {
padding: 0.25rem;
}
&--null {
opacity: 0.5;
font-style: oblique;
text-align: center;
}
&--uuid {
font-family: globals.$font-family-mono;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}

View file

@ -1,20 +0,0 @@
@use 'globals';
.collapsible-menu {
&__summary {
@include globals.reset-button;
}
&__content {
overflow: hidden;
max-height: 0;
transition: max-height 0.3s ease-in;
padding-left: 0.5rem;
&--expanded {
// todo: adjust max-height dynamically based on content
max-height: 40rem;
transition: max-height 0.3s ease-out;
}
}
}

View file

@ -1,93 +0,0 @@
@use 'sass:color';
@use 'globals';
.expression-editor {
&__container {
@include globals.rounded;
background: #eee;
display: flex;
}
&__sidebar {
display: grid;
grid-template:
'padding-top' 1fr
'operator-selector' max-content
'actions' minmax(max-content, 1fr);
}
&__main {
@include globals.rounded;
background: #fff;
border: globals.$default-border;
flex: 1;
padding: 0.5rem;
}
&__action-button {
padding: 0.5rem;
svg path {
fill: currentColor;
}
}
&__params {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
}
.expression-selector {
&__container {
grid-area: operator-selector;
}
&__expression-button {
@include globals.button-clear;
align-items: center;
display: flex;
justify-content: center;
height: 2.5rem;
padding: 0;
width: 2.5rem;
svg path {
fill: currentColor;
}
}
&__popover {
&:popover-open {
@include globals.rounded;
inset: unset;
top: anchor(bottom);
border: globals.$popover-border;
margin: 0;
margin-top: 0.25rem;
position: absolute;
display: flex;
flex-direction: column;
padding: 0;
background: #fff;
box-shadow: globals.$popover-shadow;
}
}
&__section {
align-items: center;
display: grid;
grid-template-columns: repeat(3, 1fr);
justify-content: center;
list-style-type: none;
margin: 1rem;
padding: 0;
}
&__li {
align-items: center;
display: flex;
justify-content: center;
}
}

View file

@ -1,48 +0,0 @@
@use '../globals';
@use '../forms';
@use '../viewer-shared';
:host {
height: 100%;
}
.expander__button {
@include globals.reset-button;
width: 100%;
height: 100%;
}
.header {
@include viewer-shared.th;
border-right-style: dashed;
border-bottom-style: dashed;
border-left: none;
display: flex;
&__input {
appearance: none;
background: none;
border: none;
outline: none;
font-weight: inherit;
}
}
.config-popover {
&__container {
@include globals.rounded;
font-family: globals.$font-family-default;
position: fixed;
inset: unset;
margin: 0;
width: 20rem;
padding: 1rem;
font-weight: normal;
border: globals.$popover-border;
filter: drop-shadow(globals.$popover-shadow);
&:popover-open {
display: block;
}
}
}

View file

@ -1,10 +0,0 @@
@use 'globals';
.phono-form{
&__container {
margin: 0 auto;
max-width: 48rem;
padding: 2rem;
position: relative;
}
}

View file

@ -1,212 +0,0 @@
@use 'sass:color';
@use 'basic-dropdown';
@use 'globals';
@use 'modern-normalize';
@use 'forms';
@use 'collapsible_menu';
@use 'condition-editor';
@use 'tabler-icons' with (
$ti-font-path: '../tabler-icons/webfont/fonts'
);
@use 'workspace-nav';
html {
font-family: globals.$font-family-default;
}
button, input[type="submit"] {
@include globals.reset-button;
}
@font-face {
font-family: "Averia Serif Libre";
src: url("../averia_serif_libre/averia_serif_libre_regular.ttf");
}
@font-face {
font-family: "Averia Serif Libre";
src: url("../averia_serif_libre/averia_serif_libre_bold.ttf");
font-weight: 700;
}
@font-face {
font-family: "Averia Serif Libre";
src: url("../averia_serif_libre/averia_serif_libre_light.ttf");
font-weight: 300;
}
@font-face {
font-family: "Funnel Sans";
src: url("../funnel_sans/funnel_sans_variable.ttf");
}
@view-transitions {
navigation: auto;
}
// https://css-tricks.com/inclusively-hidden/
.sr-only:not(:focus):not(:active) {
clip: rect(0 0 0 0);
clip-path: inset(50%);
height: 1px;
overflow: hidden;
position: absolute;
white-space: nowrap;
width: 1px;
}
.button {
&--primary {
@include globals.reset-anchor;
@include globals.button-primary;
}
&--secondary {
@include globals.reset-anchor;
@include globals.button-secondary;
}
&--clear {
@include globals.reset-anchor;
@include globals.button-clear;
}
&--small {
@include globals.reset-anchor;
@include globals.button-small;
}
}
.padded {
padding: 8px;
&--lg {
padding: 32px;
}
}
.page-grid {
height: 100vh;
width: 100vw;
display: grid;
grid-template:
'sidebar toolbar' 4rem
'sidebar main' 1fr / max-content 1fr;
&__toolbar {
align-items: center;
border-bottom: globals.$default-border;
display: grid;
grid-area: toolbar;
grid-template:
'utilities user' 1fr / 1fr max-content;
}
&__toolbar-utilities {
align-items: center;
display: flex;
gap: 12px;
grid-area: utilities;
justify-content: flex-start;
padding: 0 12px;
}
&__toolbar-user {
align-items: center;
display: flex;
gap: 12px;
grid-area: user;
justify-content: flex-end;
padding: 0 12px;
}
&__sidebar {
grid-area: sidebar;
width: 15rem;
max-height: 100vh;
overflow: auto;
border-right: globals.$default-border;
}
&__main {
grid-area: main;
overflow: auto;
}
}
.toolbar-item {
flex: 0;
}
.section {
padding: 1rem 2rem;
}
.notice {
@include globals.rounded;
margin: 1rem 0rem;
padding: 1rem;
max-width: 40rem;
&--info {
border: solid 1px globals.$notice-color-info;
background: color.scale(globals.$notice-color-info, $lightness: 90%, $space: hsl);
color: color.scale(globals.$notice-color-info, $lightness: -80%, $space: hsl);
}
}
.role-tree {
font-family: globals.$font-family-data;
&--no-inherit {
opacity: 0.6;
font-style: italic;
}
&__branches {
border-left: solid 1px #000;
list-style-type: none;
margin: 0;
margin-top: 0.25rem;
padding: 0;
}
&__branch {
padding-top: 0.25rem;
padding-left: 1rem;
}
}
.table {
border-collapse: collapse;
th, td {
border: globals.$default-border;
padding: 8px;
}
th {
background: #eee;
}
&__message {
opacity: 0.5;
text-align: center;
}
}
.phono-popover:popover-open {
@include globals.popover;
}
.dialog:popover-open, .dialog:open {
@include globals.rounded;
background: #fff;
border: globals.$popover-border;
box-shadow: globals.$popover-shadow;
display: block;
max-height: 90vh;
overflow: auto;
}

View file

@ -1 +0,0 @@
../static/tabler-icons/webfont/tabler-icons.scss

View file

@ -1,384 +0,0 @@
@use 'globals';
@use 'sass:color';
@use 'condition-editor';
@use 'field-adder';
$table-border-color: #ccc;
.lens-grid {
display: grid;
grid-template:
'table' 1fr
'editor' max-content;
height: 100%;
width: 100%;
}
.lens-table {
display: grid;
grid-area: table;
grid-template:
'headers' max-content
'main' 1fr
'inserter' max-content;
height: 100%;
outline: none;
overflow: auto;
width: 100%;
&__headers {
align-items: stretch;
display: flex;
grid-area: headers;
// Ensure that there will be enough space on the right for popovers to
// render without overflowing the container.
padding-right: 480px;
}
&__main {
grid-area: main;
overflow-y: auto;
}
&__row {
align-items: stretch;
display: flex;
height: 2.25rem;
}
}
.lens-cell {
align-items: stretch;
border: solid 1px $table-border-color;
border-left: none;
border-top: none;
display: flex;
flex: none;
padding: 0;
&:focus {
outline: none;
}
&--insertable {
border-style: dashed;
}
&__container {
align-items: center;
display: flex;
flex: none;
user-select: none;
width: 100%;
&--selected {
background: #07f3;
}
&--cursor {
outline: 3px solid #37f;
outline-offset: -2px;
}
}
&__content {
flex: 1;
font-family: globals.$font-family-data;
&--dropdown {
overflow: hidden;
padding: 0 8px;
}
&--numeric {
overflow: hidden;
text-align: right;
text-overflow: ellipsis;
white-space: nowrap;
padding: 0 8px;
}
&--text {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding: 0 8px;
}
&--timestamp {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding: 0 8px;
}
&--uuid {
font-family: globals.$font-family-mono;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding: 0 0.5rem;
}
&--null {
align-items: center;
color: color.scale(#000, $lightness: 65%, $space: hsl);
display: flex;
font-family: globals.$font-family-data;
font-style: oblique;
justify-content: center;
padding: 0 0.25rem;
svg path {
fill: currentColor;
}
}
}
&__notice {
align-items: center;
color: color.scale(#000, $lightness: 50%, $space: hsl);
display: flex;
padding: 0 0.25rem;
svg path {
fill: currentColor;
}
}
}
.field-header {
&__container {
align-items: center;
background: #0001;
border: solid 1px #ccc;
border-top: none;
border-left: none;
display: flex;
flex: none;
font-family: globals.$font-family-data;
justify-content: space-between;
padding: 0.25rem;
text-align: left;
}
&__label {
padding: 0.25rem;
}
&__type-indicator {
@include globals.button-clear;
align-items: center;
display: flex;
height: 2rem;
justify-content: center;
padding: 0;
width: 2rem;
svg path {
fill: currentColor;
}
}
&__popover {
&:popover-open {
@include globals.popover;
left: anchor(left);
top: anchor(bottom);
padding: 1rem;
position: absolute;
}
}
}
.lens-inserter {
grid-area: inserter;
margin-bottom: 2rem;
&__help {
font-size: 1rem;
font-weight: lighter;
margin: 8px;
opacity: 0.5;
}
&__main {
align-items: stretch;
display: flex;
justify-items: flex-start;
}
&__rows {
.lens-cell {
border: dashed 1px $table-border-color;
border-left: none;
&:last-child {
border-right: none;
}
}
.lens-table__row:first-child .lens-table__cell {
border-top: dashed 1px $table-border-color;
}
}
&__submit {
@include globals.reset-button;
align-items: center;
border: dashed 1px globals.$button-primary-background;
border-bottom-right-radius: globals.$border-radius-rounded-sm;
border-top-right-radius: globals.$border-radius-rounded-sm;
color: globals.$button-primary-background;
display: flex;
justify-content: center;
padding: 0 1rem;
svg path {
fill: currentColor;
}
}
}
.table-viewer__datum-editor {
border-top: globals.$default-border;
display: flex;
grid-area: editor;
height: 6rem;
}
.dropdown-option-badge {
background: #ccc;
border-radius: 999px;
display: block;
width: max-content;
&:not(:has(button)) {
padding: 6px 12px;
}
&--red {
background: #f99;
color: color.scale(#f99, $lightness: -66%, $space: oklch);
}
&--orange {
background: color.adjust(#f99, $hue: 50deg, $space: oklch);
color: color.scale(color.adjust(#f99, $hue: 50deg, $space: oklch), $lightness: -66%, $space: oklch);
}
&--yellow {
background: color.adjust(#f99, $hue: 100deg, $space: oklch);
color: color.scale(color.adjust(#f99, $hue: 100deg, $space: oklch), $lightness: -66%, $space: oklch);
}
&--green {
background: color.adjust(#f99, $hue: 150deg, $space: oklch);
color: color.scale(color.adjust(#f99, $hue: 150deg, $space: oklch), $lightness: -66%, $space: oklch);
}
&--blue {
background: color.adjust(#f99, $hue: 200deg, $space: oklch);
color: color.scale(color.adjust(#f99, $hue: 200deg, $space: oklch), $lightness: -66%, $space: oklch);
}
&--indigo {
background: color.adjust(#f99, $hue: 250deg, $space: oklch);
color: color.scale(color.adjust(#f99, $hue: 250deg, $space: oklch), $lightness: -66%, $space: oklch);
}
&--violet {
background: color.adjust(#f99, $hue: 300deg, $space: oklch);
color: color.scale(color.adjust(#f99, $hue: 300deg, $space: oklch), $lightness: -66%, $space: oklch);
}
button {
@include globals.button-clear;
color: inherit;
padding: 6px 12px;
}
}
.datum-editor {
&__container {
border-left: solid 4px transparent;
display: grid;
flex: 1;
grid-template: 'type-selector type-selector' max-content
'null-control value-control' max-content
'helpers helpers' auto / max-content auto;
&:has(:focus) {
border-left-color: #07f;
}
&--incomplete {
border-left-color: #f33;
}
}
&__type-selector {
grid-area: type-selector;
}
&__null-control {
@include globals.reset_button;
align-self: start;
grid-area: null-control;
padding: 0.75rem;
&--disabled {
opacity: 0.75;
}
}
&__text-input {
@include globals.reset_input;
grid-area: value-control;
font-family: globals.$font-family-data;
padding: 0.75rem 0.5rem;
}
&__timestamp-inputs {
align-items: center;
display: flex;
grid-area: value-control;
justify-content: start;
input {
@include globals.reset_input;
}
}
&__helpers {
grid-area: helpers;
overflow: auto;
}
&__dropdown-options {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 8px;
justify-content: flex-start;
margin: 0;
padding: 0 8px;
}
}
.toolbar-popover {
&:popover-open {
@include globals.rounded;
inset: unset;
border: globals.$popover-border;
margin: 0;
margin-top: 0.25rem;
position: fixed;
display: block;
width: 24rem;
padding: 0.5rem;
background: #fff;
box-shadow: globals.$popover-shadow;
}
}

View file

@ -1,35 +0,0 @@
@use 'globals';
@use 'hoverbar';
.text-editor {
width: 100%;
height: 100%;
display: flex;
align-items: stretch;
&__input {
@include globals.reset-input;
padding: 0.5rem;
font-family: globals.$font-family-data;
flex: 1;
}
}
.toggle {
&__container {
display: flex;
align-items: center;
}
&__checkbox {
margin-right: 0.25rem;
}
&__label {
margin-right: 0.5rem;
&--disabled {
opacity: 0.5;
}
}
}

View file

@ -1,80 +0,0 @@
@use 'globals';
$background-current-item: #0001;
.workspace-nav {
& h1, h2, h3, h4, h5, h6 {
margin: 0;
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
& h1 {
font-size: 1.5rem;
}
& h2 {
font-size: 1.25rem;
}
& h3 {
font-size: 1.125rem;
}
& h4, h5, h6 {
font-size: 1rem;
}
&__section {
margin-top: 16px;
}
&__menu {
list-style-type: none;
padding: 0;
margin: 0;
margin-top: 16px;
}
&__heading {
align-items: center;
display: flex;
font-size: inherit;
justify-content: space-between;
margin: 8px;
}
&__aux-button {
@include globals.button-secondary;
@include globals.button-small;
text-decoration: none;
}
&__menu-item {
display: block;
margin-left: 8px;
padding: 0;
}
&__menu-leaf {
@include globals.rounded-sm;
align-items: center;
display: flex;
justify-content: space-between;
padding: 0 8px;
&--current, &:hover {
background: $background-current-item;
}
}
&__menu-link {
color: globals.$link-color;
flex: 1;
padding: 12px 0;
text-decoration: none;
}
}

View file

@ -0,0 +1,78 @@
.expression-editor__container {
background: #eee;
border-radius: var(--default-border-radius--rounded);
display: flex;
}
.expression-editor__sidebar {
display: grid;
grid-template:
'padding-top' 1fr
'operator-selector' max-content
'actions' minmax(max-content, 1fr);
}
.expression-editor__main {
background: #fff;
border-radius: var(--default-border-radius--rounded);
border: solid 1px var(--default-border-color);
flex: 1;
padding: var(--default-padding);
}
.expression-editor__action-button {
padding: var(--default-padding);
svg path {
fill: currentColor;
}
}
.expression-editor__params {
display: flex;
flex-direction: column;
gap: var(--default-padding);
}
.expression-selector {
grid-area: operator-selector;
}
.expression-selector__expression-button {
align-items: center;
display: flex;
justify-content: center;
height: 2.5rem;
padding: 0;
width: 2.5rem;
svg path {
fill: currentColor;
}
}
.expression-selector__popover:popover-open {
top: anchor(bottom);
margin-top: 0.25rem;
position: absolute;
display: flex;
flex-direction: column;
padding: 0;
background: #fff;
}
.expression-selector__section {
align-items: center;
display: grid;
grid-template-columns: repeat(3, 1fr);
justify-content: center;
list-style-type: none;
margin: var(--default-padding);
padding: 0;
}
.expression-selector__li {
align-items: center;
display: flex;
justify-content: center;
}

480
static/main.css Normal file
View file

@ -0,0 +1,480 @@
/*
@use 'forms';
@use 'condition-editor';
*/
/* ======== Theming ======== */
:root {
--accent-color: #fc0;
--default-border-color: #ccc;
--default-border-radius--rounded: 8px;
--default-border-radius--rounded-sm: 4px;
--default-font-family:
'Funnel Sans',
'Open Sans',
'Helvetica Neue',
Arial,
sans-serif;
--default-font-family--data:
Menlo,
'Courier New',
'Open Sans',
'Helvetica Neue',
Arial,
sans-serif;
--default-font-family--mono:
Menlo,
'Courier New',
Courier,
mono;
--default-padding: 16px;
--default-padding--xs: 4px;
--default-padding--sm: 8px;
--default-padding--lg: 32px;
--a-color: #069;
--button-background--primary: var(--accent-color);
--button-background--secondary: #fff;
--button-border-color--primary: oklch(from var(--button-background--primary) calc(l * 0.9) c h);
--button-border-color--secondary: oklch(from var(--button-background--secondary) calc(l * 0.85) c h);
--button-border-radius: var(--default-border-radius--rounded);
--button-color--primary: #000;
--button-color--secondary: #000;
--button-font-family: var(--default-font-family);
--button-font-size: 1rem;
--button-font-size--small: 0.9rem;
--button-padding--default: var(--default-padding--sm) var(--default-padding);
--button-padding--small: var(--default-padding--xs) var(--default-padding--sm);
--button-padding: var(--button-padding--default);
--button-shadow: 0 2px 2px #3331;
--notice-color--info: #39d;
--popover-border-color: var(--default-border-color);
--popover-shadow: 0 8px 8px #3333;
}
/* ======== Global Setup ======== */
@font-face {
font-family: "Funnel Sans";
src: url("../funnel_sans/funnel_sans_variable.ttf");
}
@view-transitions {
navigation: auto;
}
/* https://css-tricks.com/inclusively-hidden/ */
.sr-only:not(:focus):not(:active) {
clip: rect(0 0 0 0);
clip-path: inset(50%);
height: 1px;
overflow: hidden;
position: absolute;
white-space: nowrap;
width: 1px;
}
/* ======== Basic Elements / Style Resets ======== */
html {
font-family: var(--default-font-family);
}
button {
appearance: none;
background: none;
border: none;
padding: 0;
box-sizing: border-box;
cursor: pointer;
font-family: inherit;
font-size: inherit;
font-weight: inherit;
}
input[type="text"] {
appearance: none;
background: none;
border: none;
box-sizing: border-box;
font-family: inherit;
font-size: inherit;
font-weight: inherit;
outline: none;
}
a {
color: var(--a-color);
text-decoration: none;
}
/* ======== Utility Classes ======== */
.text--data {
font-family: var(--default-font-family--data);
}
.padded {
padding: var(--default-padding);
}
.padded-sm {
padding: var(--default-padding--sm);
}
.padded--lg {
padding: var(--default-padding--lg);
}
/* ======== Components: Buttons ======== */
.button {
--button-color: inherit;
background: var(--button-background);
border: solid 1px var(--button-border-color);
border-radius: var(--button-border-radius);
box-shadow: var(--button-shadow);
color: var(--button-color);
font-family: var(--button-font-family);
font-size: var(--button-font-size);
padding: var(--button-padding);
text-decoration: none;
transition: background 0.2s ease;
&:hover {
border-color: oklch(from var(--button-border-color) calc(l * 0.95) c h);
background: oklch(from var(--button-background) calc(l * 0.95) c h);
}
}
.button--primary {
--button-background: var(--button-background--primary);
--button-border-color: var(--button-border-color--primary);
--button-color: var(--button-color--primary);
}
.button--secondary {
--button-background: var(--button-background--secondary);
--button-border-color: var(--button-border-color--secondary);
--button-color: var(--button-color--secondary);
}
.button--clear {
--button-background: transparent;
--button-border-color: transparent;
--button-color: inherit;
--button-font-family: inherit;
--button-shadow: none;
}
.button--small {
--button-font-size: var(--button-font-size--small);
--button-padding: var(--button-padding--small);
}
/* ========= Components: Popovers ======== */
.popover:popover-open {
background: #fff;
border: solid 1px var(--popover-border-color);
border-radius: var(--default-border-radius--rounded);
box-shadow: var(--popover-shadow);
display: block;
inset: unset;
margin: 0;
margin-top: 4px;
padding: 0;
position: relative;
}
.dialog:popover-open, .dialog:open {
background: #fff;
border: solid 1px var(--popover-border-color);
border-radius: var(--default-border-radius--rounded);
box-shadow: var(--popover-shadow);
display: block;
max-height: 90vh;
overflow: auto;
}
.basic-dropdown__menu {
list-style-type: none;
margin: 0;
padding: var(--default-padding--sm) 0;
& > li {
padding: 0;
&:hover {
background: #0001;
}
& > button, & > [role="button"] {
background: transparent;
color: inherit;
display: block;
font-family: var(--button-font-family--default);
padding: var(--button-padding--default);
text-decoration: none;
transition: background 0.2s ease;
}
}
}
/* ======== Components: Miscellaneous ======== */
.table {
border-collapse: collapse;
th, td {
border: solid 1px var(--default-border-color);
padding: var(--default-padding--sm);
}
th {
background: #eee;
}
.table__message {
opacity: 0.5;
text-align: center;
}
}
.section {
padding: 1rem 2rem;
}
.notice {
border-radius: var(--default-border-radius--rounded);
margin: 1rem 0rem;
padding: 1rem;
max-width: 40rem;
&.notice--info {
border: solid 1px globals.$notice-color-info;
background: oklch(from var(--notice-color--info) calc(l * 0.9) c h);
color: oklch(from var(--notice-color--info) calc(l * 0.2) c h);
}
}
.permissions-list {
display: flex;
flex-wrap: wrap;
gap: var(--default-padding--sm);
}
.permission-badge {
--permission-badge-resource-color: #f9f9f9;
--permission-badge-description-color: #eee;
display: flex;
flex-grow: 0;
flex-shrink: 0;
}
.permission-badge__resource {
background: var(--permission-badge-resource-color);
border: solid 1px oklch(from var(--permission-badge-resource-color) calc(l * 0.8) c h);
border-right: none;
border-bottom-left-radius: var(--default-border-radius--rounded-sm);
border-top-left-radius: var(--default-border-radius--rounded-sm);
font-family: var(--default-font-family--data);
font-size: 0.9rem;
padding: var(--default-padding--xs);
}
.permission-badge__description {
background: var(--permission-badge-description-color);
border: solid 1px oklch(from var(--permission-badge-description-color) calc(l * 0.8) c h);
border-bottom-right-radius: var(--default-border-radius--rounded-sm);
border-top-right-radius: var(--default-border-radius--rounded-sm);
font-family: var(--default-font-family--data);
font-size: 0.9rem;
padding: var(--default-padding--xs);
}
/* ======== Forms ======== */
.form__label {
display: block;
font-weight: 600;
margin-top: var(--default-padding);
}
.form__input {
display: block;
margin-top: var(--default-padding--sm);
font-family: var(--default-font-family--data);
&:is(input[type="text"]) {
border: solid 1px var(--default-border-color);
border-radius: var(--default-border-radius--rounded);
padding: 0.5rem;
}
}
.form__buttons {
display: flex;
margin-top: var(--default-padding);
&.form__buttons--justify-end {
justify-content: end;
}
button {
margin: 0 var(--default-padding--xs);
&:first-child {
margin-left: 0;
}
&:last-child {
margin-right: 0;
}
}
}
/* ======== Layout ======== */
.page-grid {
height: 100vh;
width: 100vw;
display: grid;
grid-template:
'sidebar toolbar' 4rem
'sidebar main' 1fr / max-content 1fr;
.page-grid__toolbar {
align-items: center;
border-bottom: solid 1px var(--default-border-color);
display: grid;
grid-area: toolbar;
grid-template: 'utilities user' 1fr / 1fr max-content;
.toolbar-item {
flex: 0;
}
}
.page-grid__toolbar-utilities {
align-items: center;
display: flex;
gap: 12px;
grid-area: utilities;
justify-content: flex-start;
padding: 0 12px;
}
.page-grid__toolbar-user {
align-items: center;
display: flex;
gap: 12px;
grid-area: user;
justify-content: flex-end;
padding: 0 12px;
basic-dropdown {
--button-background: var(--button-background--secondary);
--button-border-color: var(--button-border-color--secondary);
--button-color: var(--button-color--secondary);
}
}
.page-grid__sidebar {
grid-area: sidebar;
width: 15rem;
max-height: 100vh;
overflow: auto;
border-right: solid 1px var(--default-border-color);
}
.page-grid__main {
grid-area: main;
overflow: auto;
}
}
/* ======== Workspace Nav ======== */
.workspace-nav {
h1, h2, h3, h4, h5, h6 {
margin: 0;
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
h1 {
font-size: 1.5rem;
}
h2 {
font-size: 1.25rem;
}
h3 {
font-size: 1.125rem;
}
h4, h5, h6 {
font-size: 1rem;
}
basic-dropdown {
--button-background: var(--button-background--secondary);
--button-border-color: var(--button-border-color--secondary);
--button-color: var(--button-color--secondary);
--button-font-size: var(--button-font-size--small);
--button-padding: var(--button-padding--small);
}
.workspace-nav__section {
margin-top: var(--default-padding);
}
.workspace-nav__menu {
list-style-type: none;
padding: var(--default-padding--xs) 0;
margin: 0;
}
.workspace-nav__heading {
align-items: center;
display: flex;
font-size: inherit;
justify-content: space-between;
padding: var(--default-padding--sm);
padding-bottom: 0;
}
.workspace-nav__menu-item {
display: block;
margin-left: var(--default-padding--sm);
padding: 0;
}
.workspace-nav__menu-leaf {
align-items: center;
border-radius: var(--default-border-radius--rounded-sm);
display: flex;
justify-content: space-between;
padding: var(--default-padding--xs) var(--default-padding--sm);
.workspace-nav__menu-leaf--current, &:hover {
background: #0001;
}
}
.workspace-nav__menu-link {
flex: 1;
padding: var(--default-padding--sm) 0;
}
}

422
static/portal-table.css Normal file
View file

@ -0,0 +1,422 @@
@import "./expression-editor.css";
/* ======== Toolbar ======== */
.filter-menu {
--button-background: var(--button-background--secondary);
--button-border-color: var(--button-border-color--secondary);
--button-color: var(--button-color--secondary);
}
/* ======== Layout ======== */
.table-viewer__layout {
display: grid;
grid-template:
'table' 1fr
'editor' max-content;
height: 100%;
width: 100%;
}
.table-viewer__table {
display: grid;
grid-area: table;
grid-template:
'headers' max-content
'main' 1fr
'inserter' max-content;
height: 100%;
outline: none;
overflow: auto;
width: 100%;
}
.table-viewer__headers {
align-items: stretch;
display: flex;
grid-area: headers;
/*
Ensure that there will be enough space on the right for popovers to
render without overflowing the container.
*/
padding-right: 480px;
}
/* ======== Table Headers ======== */
.field-header,
.field-adder__header-lookalike {
align-items: center;
background: #0001;
border: solid 1px #ccc;
border-top: none;
border-left: none;
display: flex;
flex: none;
font-family: var(--default-font-family--data);
height: 100%;
justify-content: space-between;
padding: 0.25rem;
text-align: left;
}
.field-header .basic-dropdown__button {
--button-background: transparent;
--button-border-color: transparent;
--button-color: var(--button-color--secondary);
--button-shadow: none;
}
.field-header__label {
padding: 4px;
}
.field-header__type-indicator {
align-items: center;
display: flex;
justify-content: center;
padding: 0;
height: 2rem;
width: 2rem;
svg path {
fill: currentColor;
}
}
.field-header__popover:popover-open {
left: anchor(left);
top: anchor(bottom);
padding: 1rem;
position: absolute;
}
/* ======== Component: Field Adder ======== */
.field-adder {
align-items: stretch;
display: flex;
height: 100%;
}
.field-adder__summary-buttons {
align-items: center;
display: flex;
}
.field-adder__header-lookalike:not(.field-adder__header-lookalike--visible) {
display: none;
}
.field-adder__popover:popover-open {
display: grid;
font-family: var(--default-font-family);
grid-template: "completions configs" 1fr / 1fr 2fr;
left: anchor(left);
position: absolute;
top: anchor(bottom);
width: 480px;
}
.field-adder__completions {
align-items: stretch;
border-right: solid 1px var(--default-border-color);
grid-area: completions;
display: flex;
flex-direction: column;
font-family: var(--default-font-family--data);
justify-content: start;
& > button {
display: block;
padding: 0.5rem;
font-weight: normal;
text-align: left;
width: 100%;
&:hover,
&:focus,
&[aria-selected="true"] {
background: #0000001f;
}
}
}
.field-adder__configs {
grid-area: configs;
padding: var(--default-padding);
}
/* ======== Table Body ======== */
.table-viewer__main {
grid-area: main;
overflow-y: auto;
}
.table-viewer__row {
align-items: stretch;
display: flex;
height: 2.25rem;
}
.table-viewer__cell {
align-items: stretch;
border: solid 1px var(--default-border-color);
border-left: none;
border-top: none;
display: flex;
flex: none;
padding: 0;
&:focus {
outline: none;
}
.table-viewer__cell-container {
align-items: center;
display: flex;
flex: none;
user-select: none;
width: 100%;
&.table-viewer__cell-container--selected {
background: #07f3;
}
&.table-viewer__cell-container--cursor {
outline: 3px solid #37f;
outline-offset: -2px;
}
}
.table-viewer__cell-content {
flex: 1;
font-family: var(--default-font-family--data);
&.table-viewer__cell-content--dropdown {
overflow: hidden;
padding: 0 8px;
}
&.table-viewer__cell-content--numeric {
overflow: hidden;
text-align: right;
text-overflow: ellipsis;
white-space: nowrap;
padding: 0 8px;
}
&.table-viewer__cell-content--text {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding: 0 8px;
}
&.table-viewer__cell-content--timestamp {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding: 0 8px;
}
&.table-viewer__cell-content--uuid {
font-family: var(--default-font-family--mono);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding: 0 8px;
}
&.table-viewer__cell-content--null {
align-items: center;
color: #aaa;
display: flex;
font-family: var(--default-font-family--data);
font-style: oblique;
justify-content: center;
padding: 0 4px;
svg path {
fill: currentColor;
}
}
}
}
.table-viewer__notice {
align-items: center;
color: #888;
display: flex;
padding: 0 4px;
svg path {
fill: currentColor;
}
}
.table-viewer__inserter {
grid-area: inserter;
margin-bottom: 2rem;
.table-viewer__inserter-help {
font-size: 1rem;
font-weight: 300;
margin: 8px;
opacity: 0.5;
}
.table-viewer__inserter-main {
align-items: stretch;
display: flex;
justify-items: flex-start;
}
.table-viewer__inserter-rows {
.table-viewer__cell {
border: dashed 1px var(--default-border-color);
border-left: none;
&:last-child {
border-right: none;
}
}
}
.table-viewer__inserter-submit {
align-items: center;
border: dashed 1px var(--button-background--primary);
border-bottom-right-radius: var(--default-border-radius--rounded-sm);
border-top-right-radius: var(--default-border-radius--rounded-sm);
color: var(--button-background--primary);
display: flex;
justify-content: center;
padding: 0 var(--default-padding);
svg path {
fill: currentColor;
}
}
}
/* ======== Datum Editor ======== */
.datum-editor {
border-top: solid 1px var(--default-border-color);
display: flex;
grid-area: editor;
height: 6rem;
.datum-editor__container {
border-left: solid 4px transparent;
display: grid;
flex: 1;
grid-template:
'null-control value-control' max-content
'helpers helpers' auto / max-content auto;
&:has(:focus) {
border-left-color: #07f;
}
&.datum-editor__container--incomplete {
border-left-color: #f33;
}
}
.datum-editor__type-selector {
grid-area: type-selector;
}
.datum-editor__null-control {
align-self: start;
grid-area: null-control;
padding: 12px;
&.datum-editor__null-control--disabled {
opacity: 0.75;
}
}
.datum-editor__text-input {
grid-area: value-control;
font-family: var(--default-font-family--data);
padding: 12px 8px;
}
.datum-editor__timestamp-inputs {
align-items: center;
display: flex;
grid-area: value-control;
justify-content: start;
}
.datum-editor__helpers {
grid-area: helpers;
overflow: auto;
}
.datum-editor__dropdown-options {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 8px;
justify-content: flex-start;
margin: 0;
padding: 0 8px;
}
}
/* ======== Data Display ======== */
.dropdown-option-badge {
--badge-color-base: #f99;
--badge-hue-rot: 0;
--badge-background: oklch(from var(--badge-color-base) l c calc(h + var(--badge-hue-rot)));
background: var(--badge-background);
border-radius: 999px;
color: oklch(from var(--badge-background) calc(l * 0.3) c h);
display: block;
width: max-content;
&:not(:has(button)) {
padding: 6px 12px;
}
&.dropdown-option-badge--red {
--badge-hue-rot: 50;
}
&.dropdown-option-badge--orange {
--badge-hue-rot: 50;
}
&.dropdown-option-badge--yellow {
--badge-hue-rot: 100;
}
&.dropdown-option-badge--green {
--badge-hue-rot: 150;
}
&.dropdown-option-badge--blue {
--badge-hue-rot: 200;
}
&.dropdown-option-badge--indigo {
--badge-hue-rot: 250;
}
&.dropdown-option-badge--violet {
--badge-hue-rot: 300;
}
button {
color: inherit;
padding: 6px 12px;
}
}

View file

@ -3,7 +3,6 @@
// `shadowRoot` field must remain as the default, else named slots break.
props: {
button_aria_label: { attribute: "button-aria-label" },
button_class: { attribute: "button-class" },
alignment: { attribute: "alignment" },
},
tag: "basic-dropdown",
@ -30,16 +29,10 @@ stylesheet, which is merely a copy of the "main" application stylesheet.
type Props = {
alignment?: string;
button_aria_label?: string;
button_class?: string;
on_toggle?(ev: ToggleEvent): unknown;
};
let {
alignment,
button_aria_label,
button_class = "button--secondary",
on_toggle,
}: Props = $props();
let { alignment, button_aria_label, on_toggle }: Props = $props();
let popover_element: HTMLElement | undefined = $state();
// Hacky workaround because as of September 2025 implicit anchor association
@ -69,7 +62,7 @@ stylesheet, which is merely a copy of the "main" application stylesheet.
<button
aria-label={button_aria_label}
class={["basic-dropdown__button", button_class]}
class="basic-dropdown__button"
id="dropdown-button"
onclick={() => {
popover_element?.showPopover();
@ -91,6 +84,41 @@ stylesheet, which is merely a copy of the "main" application stylesheet.
<slot name="popover"></slot>
</div>
<style lang="scss">
@use "../../sass/main";
<style lang="css">
.basic-dropdown__button {
appearance: none;
box-sizing: border-box;
cursor: pointer;
font-weight: inherit;
background: var(--button-background);
border: solid 1px var(--button-border-color);
border-radius: var(--button-border-radius);
box-shadow: var(--button-shadow);
color: var(--button-color);
font-family: var(--button-font-family);
font-size: var(--button-font-size);
padding: var(--button-padding);
text-decoration: none;
transition: background 0.2s ease;
&:hover {
border-color: oklch(from var(--button-border-color) calc(l * 0.95) c h);
background: oklch(from var(--button-background) calc(l * 0.95) c h);
}
}
.basic-dropdown__popover:popover-open {
background: #fff;
border: solid 1px var(--popover-border-color);
border-radius: var(--default-border-radius--rounded);
box-shadow: var(--popover-shadow);
display: block;
margin: unset;
max-height: 90vh;
overflow: auto;
padding: 0;
position: absolute;
top: anchor(bottom);
}
</style>

View file

@ -1,51 +0,0 @@
<svelte:options
customElement={{
props: { expanded: { type: "Boolean" } },
shadow: "none",
tag: "collapsible-menu",
}}
/>
<!--
@component
An accordion-style collapsible list, designed for vertical navigation.
It exposes two named slots, "summary" (clickable and always visible) and
"content" (visible only when expanded).
-->
<script lang="ts">
type Props = {
expanded?: boolean;
};
// `expanded` is marked as $bindable to make it clear that it takes the place
// of dynamic (that is, uncontrolled) internal state as well as an externally
// controllable and inspectable value. In practice this may be used to set an
// initial value for `expanded` without the need for, e.g., an
// `initial_expanded` prop as well as an internal variable to keep track of
// the actual current state.
let { expanded = $bindable(true) }: Props = $props();
function handle_summary_click() {
expanded = !expanded;
}
</script>
<div class="collapsible-menu">
<button
class="collapsible-menu__sumary"
onclick={handle_summary_click}
type="button"
>
<slot name="summary"></slot>
</button>
<div
class={[
"collapsible-menu__content",
expanded && "collapsible-menu__content--expanded",
]}
>
<slot name="content"></slot>
</div>
</div>

View file

@ -15,21 +15,22 @@
import { type PgExpressionAny } from "./expression.svelte";
import ExpressionEditor from "./expression-editor.webc.svelte";
import { type FieldInfo } from "./field.svelte";
import { type Presentation } from "./presentation.svelte";
import { RFC_3339_S, type Presentation } from "./presentation.svelte";
import type { Datum } from "./datum.svelte";
const ASSIGNABLE_PRESENTATIONS: Presentation[] = [
{ t: "Text", c: { input_mode: { t: "MultiLine", c: {} } } },
{ t: "Timestamp", c: {} },
{ t: "Timestamp", c: { format: RFC_3339_S } },
{ t: "Uuid", c: {} },
];
const ASSIGNABLE_FIELDS: FieldInfo[] = ASSIGNABLE_PRESENTATIONS.map(
(presentation) => ({
field: {
id: "",
table_label: "",
name: "",
ordinality: -1,
presentation,
table_label: "",
table_width_px: -1,
},
not_null: true,

View file

@ -136,7 +136,7 @@
}
</script>
<div class="expression-selector__container">
<div class="expression-selector">
<button
aria-label={`Select expression type (current: ${iconography_current?.label ?? "None"})`}
bind:this={menu_button_element}
@ -154,7 +154,7 @@
</button>
<div
bind:this={popover_element}
class="expression-selector__popover"
class="popover expression-selector__popover"
popover="auto"
style:position-anchor={anchor_name}
>

View file

@ -16,7 +16,6 @@ submission.
incompatible with the current presentation configuration.-->
<script lang="ts">
import { toLowerCase } from "zod";
import FieldDetails from "./field-details.svelte";
import {
all_presentation_tags,
@ -131,9 +130,12 @@ incompatible with the current presentation configuration.-->
}
</script>
<div class="container">
<div class="field-adder">
<div
class={["header-lookalike", expanded && "visible"]}
class={[
"field-adder__header-lookalike",
expanded && "field-adder__header-lookalike--visible",
]}
style:anchor-name={anchor_name}
>
<input
@ -156,11 +158,11 @@ incompatible with the current presentation configuration.-->
<form method="post" action="add-field">
<div
bind:this={popover_element}
class="phono-popover popover"
class="popover field-adder__popover"
popover="auto"
style:position-anchor={anchor_name}
>
<div class="completions" role="listbox">
<div class="field-adder__completions" role="listbox">
{#each columns
.map(({ name }) => name)
.filter((name) => name
@ -184,7 +186,7 @@ incompatible with the current presentation configuration.-->
</button>
{/each}
</div>
<div class="configs">
<div class="field-adder__configs">
{#if presentation_value}
<FieldDetails
bind:name_value
@ -196,7 +198,9 @@ incompatible with the current presentation configuration.-->
}}
/>
{/if}
<button class="button--primary" type="submit">Create</button>
<div class="form__buttons">
<button class="button button--primary" type="submit">Create</button>
</div>
</div>
</div>
</form>
@ -205,7 +209,7 @@ incompatible with the current presentation configuration.-->
<div class="field-adder__summary-buttons">
<button
aria-label="toggle field adder"
class="button--clear"
class="button button--clear"
onclick={toggle_expanded}
type="button"
>
@ -215,83 +219,4 @@ incompatible with the current presentation configuration.-->
</div>
<style lang="css">
/*
I've been annoyed by some of the rough edges around global SCSS in web
components, and independently curious about simplifying the build process by
replacing SCSS with modern vanilla CSS entirely, so trying something new here.
TBD whether it gets adopted more widely, or reverted.
*/
@import "../../css_dist/main.css";
.container {
--default-border-color: #ccc;
--viewer-th-background: #0001;
--viewer-th-border: solid 1px #ccc;
--viewer-th-font-family: "Funnel Sans";
--viewer-th-font-weight: bolder;
--viewer-th-padding-x: 8px;
--viewer-th-padding-y: 4px;
align-items: stretch;
display: flex;
height: 100%;
}
.header-lookalike {
align-items: stretch;
background: var(--viewer-th-background);
border: var(--viewer-th-border);
border-top: none;
&:first-child {
border-left: none;
}
display: none;
&.visible {
display: flex;
}
flex-direction: column;
font-family: var(--viewer-th-font-family);
font-weight: var(--viewer-th-font-weight);
height: 100%; /* css hack to make percentage based cell heights work */
justify-content: center;
padding: var(--viewer-th-padding-y) var(--viewer-th-padding-x);
}
.popover:popover-open {
display: grid;
grid-template: "completions configs" 1fr / 1fr 2fr;
left: anchor(left);
position: absolute;
top: anchor(bottom);
width: 480px;
}
.completions {
border-right: solid 1px var(--default-border-color);
grid-area: completions;
display: flex;
flex-direction: column;
align-items: stretch;
justify-content: start;
& > button {
display: block;
padding: 0.5rem;
font-weight: normal;
text-align: left;
width: 100%;
&:hover,
&:focus,
&[aria-selected="true"] {
background: #0000001f;
}
}
}
.configs {
grid-area: configs;
padding: 16px;
}
</style>

View file

@ -100,31 +100,31 @@ field. This is typically rendered within a popover component, and within an HTML
}
</script>
<h2 class="form-section__heading">Field Details</h2>
<label class="form-section">
<div class="form-section__label">SQL-friendly Name</div>
<h2>Field Details</h2>
<label class="form__section">
<div class="form__label">SQL-friendly Name</div>
<input
bind:value={name_value}
class="form-section__input form-section__input--text"
class="form__input"
disabled={name_input_disabled}
name="name"
type="text"
/>
</label>
<label class="form-section">
<div class="form-section__label">Human-friendly Label</div>
<label class="form__section">
<div class="form__label">Human-friendly Label</div>
<input
bind:value={label_value}
class="form-section__input form-section__input--text"
class="form__input"
name="table_label"
oninput={on_name_input}
type="text"
/>
</label>
<label class="form-section">
<div class="form-section__label">Present As</div>
<label class="form__section">
<div class="form__label">Present As</div>
<select
class="form-section__input"
class="form__input"
name="presentation_tag"
onchange={handle_presentation_tag_change}
value={presentation?.t}
@ -137,7 +137,7 @@ field. This is typically rendered within a popover component, and within an HTML
</select>
</label>
{#if presentation?.t === "Dropdown"}
<div class="form-section">
<div class="form__section">
<ul class="field-details__dropdown-options-list">
{#each presentation.c.options as option, i}
<li class="field-details__dropdown-option">
@ -152,12 +152,12 @@ field. This is typically rendered within a popover component, and within an HTML
<input
type="text"
name="dropdown_option_values"
class="form-section__input--text"
class="form__input--text"
value={option.value}
/>
<button
aria-label="Remove option"
class="button--clear"
class="button button--clear"
onclick={() => {
if (presentation?.t !== "Dropdown") {
console.warn(
@ -177,7 +177,7 @@ field. This is typically rendered within a popover component, and within an HTML
{/each}
</ul>
<button
class="button--secondary"
class="button button--secondary"
onclick={() => {
if (presentation?.t !== "Dropdown") {
console.warn(
@ -196,10 +196,10 @@ field. This is typically rendered within a popover component, and within an HTML
</button>
</div>
{:else if presentation?.t === "Text"}
<label class="form-section">
<div class="form-section__label">Input Mode</div>
<label class="form__section">
<div class="form__label">Input Mode</div>
<select
class="form-section__input"
class="form__input"
name="text_input_mode"
onchange={handle_text_input_mode_change}
value={presentation.c.input_mode.t}

View file

@ -38,7 +38,7 @@
<div
aria-colindex={index}
class="field-header__container"
class="field-header"
draggable={true}
{ondragenter}
{ondragleave}
@ -52,11 +52,9 @@
<div class="field-header__label">
{field.field.table_label ?? field.field.name}
</div>
<div class="field-header__menu-container">
<BasicDropdown
alignment="right"
button_aria_label="Field options"
button_class="field-header__type-indicator"
on_toggle={(ev) => {
if (ev.newState === "closed") {
type_indicator_element?.focus();
@ -102,7 +100,6 @@
</menu>
</BasicDropdown>
</div>
</div>
<dialog bind:this={remove_field_dialog_element} class="dialog">
<form method="post" action="remove-field">
@ -116,16 +113,16 @@
Remove field and underlying data. This cannot be undone.
</label>
</div>
<div class="padded">
<div class="form__buttons">
<input type="hidden" name="field_id" value={field.field.id} />
<button class="button--primary" type="submit">Remove</button>
<button class="button button--primary" type="submit">Remove</button>
<button
class="button--secondary"
onclick={() => {
remove_field_dialog_element?.close();
}}
type="button">Cancel</button
class="button button--secondary"
onclick={() => remove_field_dialog_element?.close()}
type="button"
>
Cancel
</button>
</div>
</form>
</dialog>
@ -141,8 +138,8 @@
/>
</div>
<input type="hidden" name="field_id" value={field.field.id} />
<div class="padded">
<button class="button--primary" type="submit">Save</button>
<div class="form__buttons">
<button class="button button--primary" type="submit">Save</button>
</div>
</form>
</dialog>

View file

@ -11,6 +11,7 @@
<script lang="ts">
import { type PgExpressionAny } from "./expression.svelte";
import BasicDropdown from "./basic-dropdown.webc.svelte";
import ExpressionEditor from "./expression-editor.webc.svelte";
type Props = {
@ -20,44 +21,33 @@
let { identifier_hints = [], initialValue }: Props = $props();
let popover_element = $state<HTMLDivElement | undefined>();
let expr = $state<PgExpressionAny | undefined>(initialValue ?? undefined);
function handle_toolbar_button_click() {
popover_element?.togglePopover();
}
function handle_clear_button_click() {
expr = undefined;
}
</script>
<div class="toolbar-item">
<button
class="button--secondary"
onclick={handle_toolbar_button_click}
type="button"
>
Filter
</button>
<div bind:this={popover_element} class="toolbar-popover" popover="auto">
<form action="set-filter" method="post">
<div class="filter-menu toolbar-item">
<BasicDropdown>
<span slot="button-contents">Filter</span>
<form action="set-filter" class="padded" method="post" slot="popover">
<ExpressionEditor bind:value={expr} {identifier_hints} />
<div class="toolbar-popover__form-actions">
<div class="form__buttons">
<input
name="filter_expression"
type="hidden"
value={JSON.stringify(expr)}
/>
<button
class="button--secondary"
class="button button--secondary"
onclick={handle_clear_button_click}
type="button"
>
Clear
</button>
<button class="button--primary" type="submit">Apply</button>
<button class="button button--primary" type="submit">Apply</button>
</div>
</form>
</div>
</BasicDropdown>
</div>

View file

@ -4,6 +4,8 @@ import { type Datum } from "./datum.svelte.ts";
type Assert<_T extends true> = void;
export const RFC_3339_S = "yyyy-MM-dd'T'HH:mm:ssXXX";
export const all_presentation_tags = [
"Dropdown",
"Numeric",

View file

@ -62,7 +62,7 @@
aria-rowindex={coords.row_idx}
aria-selected={selected}
bind:this={cell_element}
class="lens-cell"
class="table-viewer__cell"
{oncopy}
ondblclick={(ev) => ondblclick?.(ev, coords)}
onfocus={(ev) => onfocus?.(ev, coords)}
@ -74,15 +74,15 @@
tabindex={selected ? 0 : -1}
>
<div
class="lens-cell__container"
class:lens-cell__container--cursor={cursor}
class:lens-cell__container--selected={selected}
class="table-viewer__cell-container"
class:table-viewer__cell-container--cursor={cursor}
class:table-viewer__cell-container--selected={selected}
>
{#if field.field.presentation.t === "Dropdown"}
{#if value.t === "Text"}
<div
class="lens-cell__content lens-cell__content--dropdown"
class:lens-cell__content--null={value.c === undefined}
class="table-viewer__cell-content table-viewer__cell-content--dropdown"
class:table-viewer__cell-content--null={value.c === undefined}
>
{#if value.c === undefined}
<i class={["ti", null_value_class]}></i>
@ -107,8 +107,8 @@
{/if}
{:else if field.field.presentation.t === "Numeric"}
<div
class="lens-cell__content lens-cell__content--numeric"
class:lens-cell__content--null={value.c === undefined}
class="table-viewer__cell-content table-viewer__cell-content--numeric"
class:table-viewer__cell-content--null={value.c === undefined}
>
{#if value.c === undefined}
<i class={["ti", null_value_class]}></i>
@ -118,8 +118,8 @@
</div>
{:else if field.field.presentation.t === "Text"}
<div
class="lens-cell__content lens-cell__content--text"
class:lens-cell__content--null={value.c === undefined}
class="table-viewer__cell-content table-viewer__cell-content--text"
class:table-viewer__cell-content--null={value.c === undefined}
>
{#if value.c === undefined}
<i class={["ti", null_value_class]}></i>
@ -129,8 +129,8 @@
</div>
{:else if field.field.presentation.t === "Uuid"}
<div
class="lens-cell__content lens-cell__content--uuid"
class:lens-cell__content--null={value.c === undefined}
class="table-viewer__cell-content table-viewer__cell-content--uuid"
class:table-viewer__cell-content--null={value.c === undefined}
>
{#if value.c === undefined}
<i class={["ti", null_value_class]}></i>
@ -140,8 +140,8 @@
</div>
{:else if field.field.presentation.t === "Timestamp"}
<div
class="lens-cell__content lens-cell__content--timestamp"
class:lens-cell__content--null={value.c === undefined}
class="table-viewer__cell-content table-viewer__cell-content--timestamp"
class:table-viewer__cell-content--null={value.c === undefined}
>
{#if value.c === undefined}
<i class={["ti", null_value_class]}></i>
@ -150,12 +150,14 @@
{/if}
</div>
{:else}
<div class="lens-cell__content lens-cell__content--unknown">
<div
class="table-viewer__cell-content table-viewer__cell-content--unknown"
>
<div>UNKNOWN</div>
</div>
{/if}
{#if invalid_value}
<div class="lens-cell__notice">
<div class="table-viewer__cell-notice">
<i class="ti ti-alert-circle"></i>
</div>
{/if}

View file

@ -572,7 +572,7 @@
})}
{#if lazy_data}
{#each rows as row, row_idx}
<div class="lens-table__row" role="row">
<div class="table-viewer__row" role="row">
{#each lazy_data.fields as field, field_idx}
<TableCell
coords={{ region, row_idx, field_idx }}
@ -604,10 +604,10 @@
{/if}
{/snippet}
<div class="lens-grid">
<div class="table-viewer__layout">
{#if lazy_data}
<div class="lens-table" role="grid">
<div class="lens-table__headers">
<div class="table-viewer__table" role="grid">
<div class="table-viewer__headers">
{#each lazy_data.fields as _, field_index}
<FieldHeader
bind:field={lazy_data.fields[field_index]}
@ -630,11 +630,11 @@
}}
/>
{/each}
<div class="lens-table__header-actions">
<div class="table-viewer__header-actions">
<FieldAdder {columns}></FieldAdder>
</div>
</div>
<div class="lens-table__main">
<div class="table-viewer__main">
{@render table_region({
region: "main",
rows: lazy_data.rows,
@ -655,12 +655,12 @@
})}
</div>
<form method="post" action="insert">
<div class="lens-inserter">
<h3 class="lens-inserter__help">
<div class="table-viewer__inserter">
<h3 class="table-viewer__inserter-help">
Insert rows &mdash; press "shift + enter" to jump here or add a row
</h3>
<div class="lens-inserter__main">
<div class="lens-inserter__rows">
<div class="table-viewer__inserter-main">
<div class="table-viewer__inserter-rows">
{@render table_region({
region: "inserter",
rows: inserter_rows,
@ -682,7 +682,7 @@
</div>
<button
aria-label="Insert rows"
class="lens-inserter__submit"
class="table-viewer__inserter-submit"
onkeydown={(ev) => {
// Prevent keypress (e.g. pressing Enter on the button to submit
// it) from triggering a table interaction.
@ -706,7 +706,7 @@
{/each}
</form>
</div>
<div class="table-viewer__datum-editor">
<div class="datum-editor">
{#if selections.length !== 0 && selections.every(({ coords: { field_idx } }) => field_idx === selections[0]?.coords.field_idx)}
<DatumEditor
bind:this={datum_editor}