Merge branch 'main' into switch-to-mixpanel
This commit is contained in:
commit
ac5d5e2451
72 changed files with 2336 additions and 1888 deletions
152
Cargo.lock
generated
152
Cargo.lock
generated
|
@ -855,22 +855,6 @@ version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "chat_panel"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"client",
|
|
||||||
"editor",
|
|
||||||
"gpui",
|
|
||||||
"menu",
|
|
||||||
"postage",
|
|
||||||
"settings",
|
|
||||||
"theme",
|
|
||||||
"time 0.3.15",
|
|
||||||
"util",
|
|
||||||
"workspace",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chrono"
|
name = "chrono"
|
||||||
version = "0.4.22"
|
version = "0.4.22"
|
||||||
|
@ -1532,9 +1516,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cxx"
|
name = "cxx"
|
||||||
version = "1.0.78"
|
version = "1.0.79"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "19f39818dcfc97d45b03953c1292efc4e80954e1583c4aa770bac1383e2310a4"
|
checksum = "3f83d0ebf42c6eafb8d7c52f7e5f2d3003b89c7aa4fd2b79229209459a849af8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"cxxbridge-flags",
|
"cxxbridge-flags",
|
||||||
|
@ -1544,9 +1528,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cxx-build"
|
name = "cxx-build"
|
||||||
version = "1.0.78"
|
version = "1.0.79"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3e580d70777c116df50c390d1211993f62d40302881e54d4b79727acb83d0199"
|
checksum = "07d050484b55975889284352b0ffc2ecbda25c0c55978017c132b29ba0818a86"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"codespan-reporting",
|
"codespan-reporting",
|
||||||
|
@ -1559,15 +1543,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cxxbridge-flags"
|
name = "cxxbridge-flags"
|
||||||
version = "1.0.78"
|
version = "1.0.79"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "56a46460b88d1cec95112c8c363f0e2c39afdb237f60583b0b36343bf627ea9c"
|
checksum = "99d2199b00553eda8012dfec8d3b1c75fce747cf27c169a270b3b99e3448ab78"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cxxbridge-macro"
|
name = "cxxbridge-macro"
|
||||||
version = "1.0.78"
|
version = "1.0.79"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "747b608fecf06b0d72d440f27acc99288207324b793be2c17991839f3d4995ea"
|
checksum = "dcb67a6de1f602736dd7eaead0080cf3435df806c61b24b13328db128c58868f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -1729,12 +1713,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dotenvy"
|
name = "dotenvy"
|
||||||
version = "0.15.5"
|
version = "0.15.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ed9155c8f4dc55c7470ae9da3f63c6785245093b3f6aeb0f5bf2e968efbba314"
|
checksum = "03d8c417d7a8cb362e0c37e5d815f5eb7c37f79ff93707329d5a194e42e54ca0"
|
||||||
dependencies = [
|
|
||||||
"dirs 4.0.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "drag_and_drop"
|
name = "drag_and_drop"
|
||||||
|
@ -2730,9 +2711,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iana-time-zone-haiku"
|
name = "iana-time-zone-haiku"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fde6edd6cef363e9359ed3c98ba64590ba9eecba2293eb5a723ab32aee8926aa"
|
checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cxx",
|
"cxx",
|
||||||
"cxx-build",
|
"cxx-build",
|
||||||
|
@ -3449,7 +3430,7 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||||
"windows-sys",
|
"windows-sys 0.36.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3878,7 +3859,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
|
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lock_api",
|
"lock_api",
|
||||||
"parking_lot_core 0.9.3",
|
"parking_lot_core 0.9.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3897,15 +3878,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot_core"
|
name = "parking_lot_core"
|
||||||
version = "0.9.3"
|
version = "0.9.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929"
|
checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
"libc",
|
"libc",
|
||||||
"redox_syscall",
|
"redox_syscall",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"windows-sys",
|
"windows-sys 0.42.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -4200,9 +4181,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.46"
|
version = "1.0.47"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b"
|
checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
@ -4926,9 +4907,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls"
|
name = "rustls"
|
||||||
version = "0.20.6"
|
version = "0.20.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033"
|
checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"ring",
|
"ring",
|
||||||
|
@ -5007,7 +4988,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2"
|
checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"windows-sys",
|
"windows-sys 0.36.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -5618,7 +5599,7 @@ dependencies = [
|
||||||
"paste",
|
"paste",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"rustls 0.20.6",
|
"rustls 0.20.7",
|
||||||
"rustls-pemfile",
|
"rustls-pemfile",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -5939,6 +5920,18 @@ dependencies = [
|
||||||
"workspace",
|
"workspace",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "theme_testbench"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"gpui",
|
||||||
|
"project",
|
||||||
|
"settings",
|
||||||
|
"smallvec",
|
||||||
|
"theme",
|
||||||
|
"workspace",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.37"
|
version = "1.0.37"
|
||||||
|
@ -6125,7 +6118,7 @@ version = "0.23.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59"
|
checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustls 0.20.6",
|
"rustls 0.20.7",
|
||||||
"tokio",
|
"tokio",
|
||||||
"webpki 0.22.0",
|
"webpki 0.22.0",
|
||||||
]
|
]
|
||||||
|
@ -7409,43 +7402,100 @@ version = "0.36.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
|
checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows_aarch64_msvc",
|
"windows_aarch64_msvc 0.36.1",
|
||||||
"windows_i686_gnu",
|
"windows_i686_gnu 0.36.1",
|
||||||
"windows_i686_msvc",
|
"windows_i686_msvc 0.36.1",
|
||||||
"windows_x86_64_gnu",
|
"windows_x86_64_gnu 0.36.1",
|
||||||
"windows_x86_64_msvc",
|
"windows_x86_64_msvc 0.36.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.42.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm",
|
||||||
|
"windows_aarch64_msvc 0.42.0",
|
||||||
|
"windows_i686_gnu 0.42.0",
|
||||||
|
"windows_i686_msvc 0.42.0",
|
||||||
|
"windows_x86_64_gnu 0.42.0",
|
||||||
|
"windows_x86_64_gnullvm",
|
||||||
|
"windows_x86_64_msvc 0.42.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.42.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_msvc"
|
name = "windows_aarch64_msvc"
|
||||||
version = "0.36.1"
|
version = "0.36.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
|
checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.42.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_gnu"
|
name = "windows_i686_gnu"
|
||||||
version = "0.36.1"
|
version = "0.36.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
|
checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.42.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_msvc"
|
name = "windows_i686_msvc"
|
||||||
version = "0.36.1"
|
version = "0.36.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
|
checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.42.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnu"
|
name = "windows_x86_64_gnu"
|
||||||
version = "0.36.1"
|
version = "0.36.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
|
checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.42.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.42.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_msvc"
|
name = "windows_x86_64_msvc"
|
||||||
version = "0.36.1"
|
version = "0.36.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
|
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.42.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winreg"
|
name = "winreg"
|
||||||
version = "0.10.1"
|
version = "0.10.1"
|
||||||
|
@ -7532,9 +7582,9 @@ checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xmlparser"
|
name = "xmlparser"
|
||||||
version = "0.13.3"
|
version = "0.13.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "114ba2b24d2167ef6d67d7d04c8cc86522b87f490025f39f0303b7db5bf5e3d8"
|
checksum = "4d25c75bf9ea12c4040a97f829154768bbbce366287e2dc044af160cd79a13fd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xmlwriter"
|
name = "xmlwriter"
|
||||||
|
@ -7565,7 +7615,6 @@ dependencies = [
|
||||||
"backtrace",
|
"backtrace",
|
||||||
"breadcrumbs",
|
"breadcrumbs",
|
||||||
"call",
|
"call",
|
||||||
"chat_panel",
|
|
||||||
"chrono",
|
"chrono",
|
||||||
"cli",
|
"cli",
|
||||||
"client",
|
"client",
|
||||||
|
@ -7624,6 +7673,7 @@ dependencies = [
|
||||||
"text",
|
"text",
|
||||||
"theme",
|
"theme",
|
||||||
"theme_selector",
|
"theme_selector",
|
||||||
|
"theme_testbench",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tiny_http",
|
"tiny_http",
|
||||||
"toml",
|
"toml",
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "chat_panel"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
path = "src/chat_panel.rs"
|
|
||||||
doctest = false
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
client = { path = "../client" }
|
|
||||||
editor = { path = "../editor" }
|
|
||||||
gpui = { path = "../gpui" }
|
|
||||||
menu = { path = "../menu" }
|
|
||||||
settings = { path = "../settings" }
|
|
||||||
theme = { path = "../theme" }
|
|
||||||
util = { path = "../util" }
|
|
||||||
workspace = { path = "../workspace" }
|
|
||||||
postage = { version = "0.4.1", features = ["futures-traits"] }
|
|
||||||
time = { version = "0.3", features = ["serde", "serde-well-known"] }
|
|
|
@ -1,433 +0,0 @@
|
||||||
use client::{
|
|
||||||
channel::{Channel, ChannelEvent, ChannelList, ChannelMessage},
|
|
||||||
Client,
|
|
||||||
};
|
|
||||||
use editor::Editor;
|
|
||||||
use gpui::{
|
|
||||||
actions,
|
|
||||||
elements::*,
|
|
||||||
platform::CursorStyle,
|
|
||||||
views::{ItemType, Select, SelectStyle},
|
|
||||||
AnyViewHandle, AppContext, Entity, ModelHandle, MouseButton, MutableAppContext, RenderContext,
|
|
||||||
Subscription, Task, View, ViewContext, ViewHandle,
|
|
||||||
};
|
|
||||||
use menu::Confirm;
|
|
||||||
use postage::prelude::Stream;
|
|
||||||
use settings::{Settings, SoftWrap};
|
|
||||||
use std::sync::Arc;
|
|
||||||
use time::{OffsetDateTime, UtcOffset};
|
|
||||||
use util::{ResultExt, TryFutureExt};
|
|
||||||
|
|
||||||
const MESSAGE_LOADING_THRESHOLD: usize = 50;
|
|
||||||
|
|
||||||
pub struct ChatPanel {
|
|
||||||
rpc: Arc<Client>,
|
|
||||||
channel_list: ModelHandle<ChannelList>,
|
|
||||||
active_channel: Option<(ModelHandle<Channel>, Subscription)>,
|
|
||||||
message_list: ListState,
|
|
||||||
input_editor: ViewHandle<Editor>,
|
|
||||||
channel_select: ViewHandle<Select>,
|
|
||||||
local_timezone: UtcOffset,
|
|
||||||
_observe_status: Task<()>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum Event {}
|
|
||||||
|
|
||||||
actions!(chat_panel, [LoadMoreMessages]);
|
|
||||||
|
|
||||||
pub fn init(cx: &mut MutableAppContext) {
|
|
||||||
cx.add_action(ChatPanel::send);
|
|
||||||
cx.add_action(ChatPanel::load_more_messages);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ChatPanel {
|
|
||||||
pub fn new(
|
|
||||||
rpc: Arc<Client>,
|
|
||||||
channel_list: ModelHandle<ChannelList>,
|
|
||||||
cx: &mut ViewContext<Self>,
|
|
||||||
) -> Self {
|
|
||||||
let input_editor = cx.add_view(|cx| {
|
|
||||||
let mut editor =
|
|
||||||
Editor::auto_height(4, Some(|theme| theme.chat_panel.input_editor.clone()), cx);
|
|
||||||
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
|
|
||||||
editor
|
|
||||||
});
|
|
||||||
let channel_select = cx.add_view(|cx| {
|
|
||||||
let channel_list = channel_list.clone();
|
|
||||||
Select::new(0, cx, {
|
|
||||||
move |ix, item_type, is_hovered, cx| {
|
|
||||||
Self::render_channel_name(
|
|
||||||
&channel_list,
|
|
||||||
ix,
|
|
||||||
item_type,
|
|
||||||
is_hovered,
|
|
||||||
&cx.global::<Settings>().theme.chat_panel.channel_select,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.with_style(move |cx| {
|
|
||||||
let theme = &cx.global::<Settings>().theme.chat_panel.channel_select;
|
|
||||||
SelectStyle {
|
|
||||||
header: theme.header.container,
|
|
||||||
menu: theme.menu,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut message_list = ListState::new(0, Orientation::Bottom, 1000., cx, {
|
|
||||||
let this = cx.weak_handle();
|
|
||||||
move |_, ix, cx| {
|
|
||||||
let this = this.upgrade(cx).unwrap().read(cx);
|
|
||||||
let message = this.active_channel.as_ref().unwrap().0.read(cx).message(ix);
|
|
||||||
this.render_message(message, cx)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
message_list.set_scroll_handler(|visible_range, cx| {
|
|
||||||
if visible_range.start < MESSAGE_LOADING_THRESHOLD {
|
|
||||||
cx.dispatch_action(LoadMoreMessages);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let _observe_status = cx.spawn_weak(|this, mut cx| {
|
|
||||||
let mut status = rpc.status();
|
|
||||||
async move {
|
|
||||||
while (status.recv().await).is_some() {
|
|
||||||
if let Some(this) = this.upgrade(&cx) {
|
|
||||||
this.update(&mut cx, |_, cx| cx.notify());
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut this = Self {
|
|
||||||
rpc,
|
|
||||||
channel_list,
|
|
||||||
active_channel: Default::default(),
|
|
||||||
message_list,
|
|
||||||
input_editor,
|
|
||||||
channel_select,
|
|
||||||
local_timezone: cx.platform().local_timezone(),
|
|
||||||
_observe_status,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.init_active_channel(cx);
|
|
||||||
cx.observe(&this.channel_list, |this, _, cx| {
|
|
||||||
this.init_active_channel(cx);
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
cx.observe(&this.channel_select, |this, channel_select, cx| {
|
|
||||||
let selected_ix = channel_select.read(cx).selected_index();
|
|
||||||
let selected_channel = this.channel_list.update(cx, |channel_list, cx| {
|
|
||||||
let available_channels = channel_list.available_channels()?;
|
|
||||||
let channel_id = available_channels.get(selected_ix)?.id;
|
|
||||||
channel_list.get_channel(channel_id, cx)
|
|
||||||
});
|
|
||||||
if let Some(selected_channel) = selected_channel {
|
|
||||||
this.set_active_channel(selected_channel, cx);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
|
|
||||||
this
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init_active_channel(&mut self, cx: &mut ViewContext<Self>) {
|
|
||||||
let (active_channel, channel_count) = self.channel_list.update(cx, |list, cx| {
|
|
||||||
let channel_count;
|
|
||||||
let mut active_channel = None;
|
|
||||||
|
|
||||||
if let Some(available_channels) = list.available_channels() {
|
|
||||||
channel_count = available_channels.len();
|
|
||||||
if self.active_channel.is_none() {
|
|
||||||
if let Some(channel_id) = available_channels.first().map(|channel| channel.id) {
|
|
||||||
active_channel = list.get_channel(channel_id, cx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
channel_count = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
(active_channel, channel_count)
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(active_channel) = active_channel {
|
|
||||||
self.set_active_channel(active_channel, cx);
|
|
||||||
} else {
|
|
||||||
self.message_list.reset(0);
|
|
||||||
self.active_channel = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.channel_select.update(cx, |select, cx| {
|
|
||||||
select.set_item_count(channel_count, cx);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_active_channel(&mut self, channel: ModelHandle<Channel>, cx: &mut ViewContext<Self>) {
|
|
||||||
if self.active_channel.as_ref().map(|e| &e.0) != Some(&channel) {
|
|
||||||
{
|
|
||||||
let channel = channel.read(cx);
|
|
||||||
self.message_list.reset(channel.message_count());
|
|
||||||
let placeholder = format!("Message #{}", channel.name());
|
|
||||||
self.input_editor.update(cx, move |editor, cx| {
|
|
||||||
editor.set_placeholder_text(placeholder, cx);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
let subscription = cx.subscribe(&channel, Self::channel_did_change);
|
|
||||||
self.active_channel = Some((channel, subscription));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn channel_did_change(
|
|
||||||
&mut self,
|
|
||||||
_: ModelHandle<Channel>,
|
|
||||||
event: &ChannelEvent,
|
|
||||||
cx: &mut ViewContext<Self>,
|
|
||||||
) {
|
|
||||||
match event {
|
|
||||||
ChannelEvent::MessagesUpdated {
|
|
||||||
old_range,
|
|
||||||
new_count,
|
|
||||||
} => {
|
|
||||||
self.message_list.splice(old_range.clone(), *new_count);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_channel(&self, cx: &mut RenderContext<Self>) -> ElementBox {
|
|
||||||
let theme = &cx.global::<Settings>().theme;
|
|
||||||
Flex::column()
|
|
||||||
.with_child(
|
|
||||||
Container::new(ChildView::new(&self.channel_select, cx).boxed())
|
|
||||||
.with_style(theme.chat_panel.channel_select.container)
|
|
||||||
.boxed(),
|
|
||||||
)
|
|
||||||
.with_child(self.render_active_channel_messages())
|
|
||||||
.with_child(self.render_input_box(cx))
|
|
||||||
.boxed()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_active_channel_messages(&self) -> ElementBox {
|
|
||||||
let messages = if self.active_channel.is_some() {
|
|
||||||
List::new(self.message_list.clone()).boxed()
|
|
||||||
} else {
|
|
||||||
Empty::new().boxed()
|
|
||||||
};
|
|
||||||
|
|
||||||
FlexItem::new(messages).flex(1., true).boxed()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_message(&self, message: &ChannelMessage, cx: &AppContext) -> ElementBox {
|
|
||||||
let now = OffsetDateTime::now_utc();
|
|
||||||
let settings = cx.global::<Settings>();
|
|
||||||
let theme = if message.is_pending() {
|
|
||||||
&settings.theme.chat_panel.pending_message
|
|
||||||
} else {
|
|
||||||
&settings.theme.chat_panel.message
|
|
||||||
};
|
|
||||||
|
|
||||||
Container::new(
|
|
||||||
Flex::column()
|
|
||||||
.with_child(
|
|
||||||
Flex::row()
|
|
||||||
.with_child(
|
|
||||||
Container::new(
|
|
||||||
Label::new(
|
|
||||||
message.sender.github_login.clone(),
|
|
||||||
theme.sender.text.clone(),
|
|
||||||
)
|
|
||||||
.boxed(),
|
|
||||||
)
|
|
||||||
.with_style(theme.sender.container)
|
|
||||||
.boxed(),
|
|
||||||
)
|
|
||||||
.with_child(
|
|
||||||
Container::new(
|
|
||||||
Label::new(
|
|
||||||
format_timestamp(message.timestamp, now, self.local_timezone),
|
|
||||||
theme.timestamp.text.clone(),
|
|
||||||
)
|
|
||||||
.boxed(),
|
|
||||||
)
|
|
||||||
.with_style(theme.timestamp.container)
|
|
||||||
.boxed(),
|
|
||||||
)
|
|
||||||
.boxed(),
|
|
||||||
)
|
|
||||||
.with_child(Text::new(message.body.clone(), theme.body.clone()).boxed())
|
|
||||||
.boxed(),
|
|
||||||
)
|
|
||||||
.with_style(theme.container)
|
|
||||||
.boxed()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_input_box(&self, cx: &AppContext) -> ElementBox {
|
|
||||||
let theme = &cx.global::<Settings>().theme;
|
|
||||||
Container::new(ChildView::new(&self.input_editor, cx).boxed())
|
|
||||||
.with_style(theme.chat_panel.input_editor.container)
|
|
||||||
.boxed()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_channel_name(
|
|
||||||
channel_list: &ModelHandle<ChannelList>,
|
|
||||||
ix: usize,
|
|
||||||
item_type: ItemType,
|
|
||||||
is_hovered: bool,
|
|
||||||
theme: &theme::ChannelSelect,
|
|
||||||
cx: &AppContext,
|
|
||||||
) -> ElementBox {
|
|
||||||
let channel = &channel_list.read(cx).available_channels().unwrap()[ix];
|
|
||||||
let theme = match (item_type, is_hovered) {
|
|
||||||
(ItemType::Header, _) => &theme.header,
|
|
||||||
(ItemType::Selected, false) => &theme.active_item,
|
|
||||||
(ItemType::Selected, true) => &theme.hovered_active_item,
|
|
||||||
(ItemType::Unselected, false) => &theme.item,
|
|
||||||
(ItemType::Unselected, true) => &theme.hovered_item,
|
|
||||||
};
|
|
||||||
Container::new(
|
|
||||||
Flex::row()
|
|
||||||
.with_child(
|
|
||||||
Container::new(Label::new("#".to_string(), theme.hash.text.clone()).boxed())
|
|
||||||
.with_style(theme.hash.container)
|
|
||||||
.boxed(),
|
|
||||||
)
|
|
||||||
.with_child(Label::new(channel.name.clone(), theme.name.clone()).boxed())
|
|
||||||
.boxed(),
|
|
||||||
)
|
|
||||||
.with_style(theme.container)
|
|
||||||
.boxed()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_sign_in_prompt(&self, cx: &mut RenderContext<Self>) -> ElementBox {
|
|
||||||
let theme = cx.global::<Settings>().theme.clone();
|
|
||||||
let rpc = self.rpc.clone();
|
|
||||||
let this = cx.handle();
|
|
||||||
|
|
||||||
enum SignInPromptLabel {}
|
|
||||||
|
|
||||||
Align::new(
|
|
||||||
MouseEventHandler::<SignInPromptLabel>::new(0, cx, |mouse_state, _| {
|
|
||||||
Label::new(
|
|
||||||
"Sign in to use chat".to_string(),
|
|
||||||
if mouse_state.hovered() {
|
|
||||||
theme.chat_panel.hovered_sign_in_prompt.clone()
|
|
||||||
} else {
|
|
||||||
theme.chat_panel.sign_in_prompt.clone()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.boxed()
|
|
||||||
})
|
|
||||||
.with_cursor_style(CursorStyle::PointingHand)
|
|
||||||
.on_click(MouseButton::Left, move |_, cx| {
|
|
||||||
let rpc = rpc.clone();
|
|
||||||
let this = this.clone();
|
|
||||||
cx.spawn(|mut cx| async move {
|
|
||||||
if rpc
|
|
||||||
.authenticate_and_connect(true, &cx)
|
|
||||||
.log_err()
|
|
||||||
.await
|
|
||||||
.is_some()
|
|
||||||
{
|
|
||||||
cx.update(|cx| {
|
|
||||||
if let Some(this) = this.upgrade(cx) {
|
|
||||||
if this.is_focused(cx) {
|
|
||||||
this.update(cx, |this, cx| cx.focus(&this.input_editor));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
})
|
|
||||||
.boxed(),
|
|
||||||
)
|
|
||||||
.boxed()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
|
|
||||||
if let Some((channel, _)) = self.active_channel.as_ref() {
|
|
||||||
let body = self.input_editor.update(cx, |editor, cx| {
|
|
||||||
let body = editor.text(cx);
|
|
||||||
editor.clear(cx);
|
|
||||||
body
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(task) = channel
|
|
||||||
.update(cx, |channel, cx| channel.send_message(body, cx))
|
|
||||||
.log_err()
|
|
||||||
{
|
|
||||||
task.detach();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_more_messages(&mut self, _: &LoadMoreMessages, cx: &mut ViewContext<Self>) {
|
|
||||||
if let Some((channel, _)) = self.active_channel.as_ref() {
|
|
||||||
channel.update(cx, |channel, cx| {
|
|
||||||
channel.load_more_messages(cx);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Entity for ChatPanel {
|
|
||||||
type Event = Event;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl View for ChatPanel {
|
|
||||||
fn ui_name() -> &'static str {
|
|
||||||
"ChatPanel"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
|
||||||
let element = if self.rpc.user_id().is_some() {
|
|
||||||
self.render_channel(cx)
|
|
||||||
} else {
|
|
||||||
self.render_sign_in_prompt(cx)
|
|
||||||
};
|
|
||||||
let theme = &cx.global::<Settings>().theme;
|
|
||||||
ConstrainedBox::new(
|
|
||||||
Container::new(element)
|
|
||||||
.with_style(theme.chat_panel.container)
|
|
||||||
.boxed(),
|
|
||||||
)
|
|
||||||
.with_min_width(150.)
|
|
||||||
.boxed()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
|
|
||||||
if matches!(
|
|
||||||
*self.rpc.status().borrow(),
|
|
||||||
client::Status::Connected { .. }
|
|
||||||
) {
|
|
||||||
cx.focus(&self.input_editor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn format_timestamp(
|
|
||||||
mut timestamp: OffsetDateTime,
|
|
||||||
mut now: OffsetDateTime,
|
|
||||||
local_timezone: UtcOffset,
|
|
||||||
) -> String {
|
|
||||||
timestamp = timestamp.to_offset(local_timezone);
|
|
||||||
now = now.to_offset(local_timezone);
|
|
||||||
|
|
||||||
let today = now.date();
|
|
||||||
let date = timestamp.date();
|
|
||||||
let mut hour = timestamp.hour();
|
|
||||||
let mut part = "am";
|
|
||||||
if hour > 12 {
|
|
||||||
hour -= 12;
|
|
||||||
part = "pm";
|
|
||||||
}
|
|
||||||
if date == today {
|
|
||||||
format!("{:02}:{:02}{}", hour, timestamp.minute(), part)
|
|
||||||
} else if date.next_day() == Some(today) {
|
|
||||||
format!("yesterday at {:02}:{:02}{}", hour, timestamp.minute(), part)
|
|
||||||
} else {
|
|
||||||
format!("{:02}/{}/{}", date.month() as u32, date.day(), date.year())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -13,11 +13,13 @@ use async_tungstenite::tungstenite::{
|
||||||
http::{Request, StatusCode},
|
http::{Request, StatusCode},
|
||||||
};
|
};
|
||||||
use db::Db;
|
use db::Db;
|
||||||
use futures::{future::LocalBoxFuture, FutureExt, SinkExt, StreamExt, TryStreamExt};
|
use futures::{future::LocalBoxFuture, AsyncReadExt, FutureExt, SinkExt, StreamExt, TryStreamExt};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, serde_json::Value, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle,
|
actions,
|
||||||
AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle,
|
serde_json::{self, Value},
|
||||||
MutableAppContext, Task, View, ViewContext, ViewHandle,
|
AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AnyWeakViewHandle, AppContext,
|
||||||
|
AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task, View, ViewContext,
|
||||||
|
ViewHandle,
|
||||||
};
|
};
|
||||||
use http::HttpClient;
|
use http::HttpClient;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
@ -25,6 +27,7 @@ use parking_lot::RwLock;
|
||||||
use postage::watch;
|
use postage::watch;
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use rpc::proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, RequestMessage};
|
use rpc::proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, RequestMessage};
|
||||||
|
use serde::Deserialize;
|
||||||
use std::{
|
use std::{
|
||||||
any::TypeId,
|
any::TypeId,
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
|
@ -50,6 +53,9 @@ lazy_static! {
|
||||||
pub static ref IMPERSONATE_LOGIN: Option<String> = std::env::var("ZED_IMPERSONATE")
|
pub static ref IMPERSONATE_LOGIN: Option<String> = std::env::var("ZED_IMPERSONATE")
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|s| if s.is_empty() { None } else { Some(s) });
|
.and_then(|s| if s.is_empty() { None } else { Some(s) });
|
||||||
|
pub static ref ADMIN_API_TOKEN: Option<String> = std::env::var("ZED_ADMIN_API_TOKEN")
|
||||||
|
.ok()
|
||||||
|
.and_then(|s| if s.is_empty() { None } else { Some(s) });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const ZED_SECRET_CLIENT_TOKEN: &str = "618033988749894";
|
pub const ZED_SECRET_CLIENT_TOKEN: &str = "618033988749894";
|
||||||
|
@ -919,6 +925,37 @@ impl Client {
|
||||||
self.establish_websocket_connection(credentials, cx)
|
self.establish_websocket_connection(credentials, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_rpc_url(http: Arc<dyn HttpClient>) -> Result<Url> {
|
||||||
|
let url = format!("{}/rpc", *ZED_SERVER_URL);
|
||||||
|
let response = http.get(&url, Default::default(), false).await?;
|
||||||
|
|
||||||
|
// Normally, ZED_SERVER_URL is set to the URL of zed.dev website.
|
||||||
|
// The website's /rpc endpoint redirects to a collab server's /rpc endpoint,
|
||||||
|
// which requires authorization via an HTTP header.
|
||||||
|
//
|
||||||
|
// For testing purposes, ZED_SERVER_URL can also set to the direct URL of
|
||||||
|
// of a collab server. In that case, a request to the /rpc endpoint will
|
||||||
|
// return an 'unauthorized' response.
|
||||||
|
let collab_url = if response.status().is_redirection() {
|
||||||
|
response
|
||||||
|
.headers()
|
||||||
|
.get("Location")
|
||||||
|
.ok_or_else(|| anyhow!("missing location header in /rpc response"))?
|
||||||
|
.to_str()
|
||||||
|
.map_err(EstablishConnectionError::other)?
|
||||||
|
.to_string()
|
||||||
|
} else if response.status() == StatusCode::UNAUTHORIZED {
|
||||||
|
url
|
||||||
|
} else {
|
||||||
|
Err(anyhow!(
|
||||||
|
"unexpected /rpc response status {}",
|
||||||
|
response.status()
|
||||||
|
))?
|
||||||
|
};
|
||||||
|
|
||||||
|
Url::parse(&collab_url).context("invalid rpc url")
|
||||||
|
}
|
||||||
|
|
||||||
fn establish_websocket_connection(
|
fn establish_websocket_connection(
|
||||||
self: &Arc<Self>,
|
self: &Arc<Self>,
|
||||||
credentials: &Credentials,
|
credentials: &Credentials,
|
||||||
|
@ -933,28 +970,7 @@ impl Client {
|
||||||
|
|
||||||
let http = self.http.clone();
|
let http = self.http.clone();
|
||||||
cx.background().spawn(async move {
|
cx.background().spawn(async move {
|
||||||
let mut rpc_url = format!("{}/rpc", *ZED_SERVER_URL);
|
let mut rpc_url = Self::get_rpc_url(http).await?;
|
||||||
let rpc_response = http.get(&rpc_url, Default::default(), false).await?;
|
|
||||||
if rpc_response.status().is_redirection() {
|
|
||||||
rpc_url = rpc_response
|
|
||||||
.headers()
|
|
||||||
.get("Location")
|
|
||||||
.ok_or_else(|| anyhow!("missing location header in /rpc response"))?
|
|
||||||
.to_str()
|
|
||||||
.map_err(EstablishConnectionError::other)?
|
|
||||||
.to_string();
|
|
||||||
}
|
|
||||||
// Until we switch the zed.dev domain to point to the new Next.js app, there
|
|
||||||
// will be no redirect required, and the app will connect directly to
|
|
||||||
// wss://zed.dev/rpc.
|
|
||||||
else if rpc_response.status() != StatusCode::UPGRADE_REQUIRED {
|
|
||||||
Err(anyhow!(
|
|
||||||
"unexpected /rpc response status {}",
|
|
||||||
rpc_response.status()
|
|
||||||
))?
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut rpc_url = Url::parse(&rpc_url).context("invalid rpc url")?;
|
|
||||||
let rpc_host = rpc_url
|
let rpc_host = rpc_url
|
||||||
.host_str()
|
.host_str()
|
||||||
.zip(rpc_url.port_or_known_default())
|
.zip(rpc_url.port_or_known_default())
|
||||||
|
@ -997,6 +1013,7 @@ impl Client {
|
||||||
let platform = cx.platform();
|
let platform = cx.platform();
|
||||||
let executor = cx.background();
|
let executor = cx.background();
|
||||||
let telemetry = self.telemetry.clone();
|
let telemetry = self.telemetry.clone();
|
||||||
|
let http = self.http.clone();
|
||||||
executor.clone().spawn(async move {
|
executor.clone().spawn(async move {
|
||||||
// Generate a pair of asymmetric encryption keys. The public key will be used by the
|
// Generate a pair of asymmetric encryption keys. The public key will be used by the
|
||||||
// zed server to encrypt the user's access token, so that it can'be intercepted by
|
// zed server to encrypt the user's access token, so that it can'be intercepted by
|
||||||
|
@ -1006,6 +1023,10 @@ impl Client {
|
||||||
let public_key_string =
|
let public_key_string =
|
||||||
String::try_from(public_key).expect("failed to serialize public key for auth");
|
String::try_from(public_key).expect("failed to serialize public key for auth");
|
||||||
|
|
||||||
|
if let Some((login, token)) = IMPERSONATE_LOGIN.as_ref().zip(ADMIN_API_TOKEN.as_ref()) {
|
||||||
|
return Self::authenticate_as_admin(http, login.clone(), token.clone()).await;
|
||||||
|
}
|
||||||
|
|
||||||
// Start an HTTP server to receive the redirect from Zed's sign-in page.
|
// Start an HTTP server to receive the redirect from Zed's sign-in page.
|
||||||
let server = tiny_http::Server::http("127.0.0.1:0").expect("failed to find open port");
|
let server = tiny_http::Server::http("127.0.0.1:0").expect("failed to find open port");
|
||||||
let port = server.server_addr().port();
|
let port = server.server_addr().port();
|
||||||
|
@ -1084,6 +1105,50 @@ impl Client {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn authenticate_as_admin(
|
||||||
|
http: Arc<dyn HttpClient>,
|
||||||
|
login: String,
|
||||||
|
mut api_token: String,
|
||||||
|
) -> Result<Credentials> {
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct AuthenticatedUserResponse {
|
||||||
|
user: User,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct User {
|
||||||
|
id: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the collab server's admin API to retrieve the id
|
||||||
|
// of the impersonated user.
|
||||||
|
let mut url = Self::get_rpc_url(http.clone()).await?;
|
||||||
|
url.set_path("/user");
|
||||||
|
url.set_query(Some(&format!("github_login={login}")));
|
||||||
|
let request = Request::get(url.as_str())
|
||||||
|
.header("Authorization", format!("token {api_token}"))
|
||||||
|
.body("".into())?;
|
||||||
|
|
||||||
|
let mut response = http.send(request).await?;
|
||||||
|
let mut body = String::new();
|
||||||
|
response.body_mut().read_to_string(&mut body).await?;
|
||||||
|
if !response.status().is_success() {
|
||||||
|
Err(anyhow!(
|
||||||
|
"admin user request failed {} - {}",
|
||||||
|
response.status().as_u16(),
|
||||||
|
body,
|
||||||
|
))?;
|
||||||
|
}
|
||||||
|
let response: AuthenticatedUserResponse = serde_json::from_str(&body)?;
|
||||||
|
|
||||||
|
// Use the admin API token to authenticate as the impersonated user.
|
||||||
|
api_token.insert_str(0, "ADMIN_TOKEN:");
|
||||||
|
Ok(Credentials {
|
||||||
|
user_id: response.user.id,
|
||||||
|
access_token: api_token,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn disconnect(self: &Arc<Self>, cx: &AsyncAppContext) -> Result<()> {
|
pub fn disconnect(self: &Arc<Self>, cx: &AsyncAppContext) -> Result<()> {
|
||||||
let conn_id = self.connection_id()?;
|
let conn_id = self.connection_id()?;
|
||||||
self.peer.disconnect(conn_id);
|
self.peer.disconnect(conn_id);
|
||||||
|
|
|
@ -3,7 +3,5 @@ HTTP_PORT = 8080
|
||||||
API_TOKEN = "secret"
|
API_TOKEN = "secret"
|
||||||
INVITE_LINK_PREFIX = "http://localhost:3000/invites/"
|
INVITE_LINK_PREFIX = "http://localhost:3000/invites/"
|
||||||
|
|
||||||
# HONEYCOMB_API_KEY=
|
|
||||||
# HONEYCOMB_DATASET=
|
|
||||||
# RUST_LOG=info
|
# RUST_LOG=info
|
||||||
# LOG_JSON=true
|
# LOG_JSON=true
|
||||||
|
|
|
@ -65,31 +65,6 @@ spec:
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
name: database
|
name: database
|
||||||
key: url
|
key: url
|
||||||
- name: SESSION_SECRET
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: session
|
|
||||||
key: secret
|
|
||||||
- name: GITHUB_APP_ID
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: github
|
|
||||||
key: appId
|
|
||||||
- name: GITHUB_CLIENT_ID
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: github
|
|
||||||
key: clientId
|
|
||||||
- name: GITHUB_CLIENT_SECRET
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: github
|
|
||||||
key: clientSecret
|
|
||||||
- name: GITHUB_PRIVATE_KEY
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: github
|
|
||||||
key: privateKey
|
|
||||||
- name: API_TOKEN
|
- name: API_TOKEN
|
||||||
valueFrom:
|
valueFrom:
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
|
@ -101,13 +76,6 @@ spec:
|
||||||
value: ${RUST_LOG}
|
value: ${RUST_LOG}
|
||||||
- name: LOG_JSON
|
- name: LOG_JSON
|
||||||
value: "true"
|
value: "true"
|
||||||
- name: HONEYCOMB_DATASET
|
|
||||||
value: "collab"
|
|
||||||
- name: HONEYCOMB_API_KEY
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: honeycomb
|
|
||||||
key: apiKey
|
|
||||||
securityContext:
|
securityContext:
|
||||||
capabilities:
|
capabilities:
|
||||||
# FIXME - Switch to the more restrictive `PERFMON` capability.
|
# FIXME - Switch to the more restrictive `PERFMON` capability.
|
||||||
|
|
|
@ -76,7 +76,7 @@ pub async fn validate_api_token<B>(req: Request<B>, next: Next<B>) -> impl IntoR
|
||||||
|
|
||||||
let state = req.extensions().get::<Arc<AppState>>().unwrap();
|
let state = req.extensions().get::<Arc<AppState>>().unwrap();
|
||||||
|
|
||||||
if token != state.api_token {
|
if token != state.config.api_token {
|
||||||
Err(Error::Http(
|
Err(Error::Http(
|
||||||
StatusCode::UNAUTHORIZED,
|
StatusCode::UNAUTHORIZED,
|
||||||
"invalid authorization token".to_string(),
|
"invalid authorization token".to_string(),
|
||||||
|
@ -88,7 +88,7 @@ pub async fn validate_api_token<B>(req: Request<B>, next: Next<B>) -> impl IntoR
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct AuthenticatedUserParams {
|
struct AuthenticatedUserParams {
|
||||||
github_user_id: i32,
|
github_user_id: Option<i32>,
|
||||||
github_login: String,
|
github_login: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,7 +104,7 @@ async fn get_authenticated_user(
|
||||||
) -> Result<Json<AuthenticatedUserResponse>> {
|
) -> Result<Json<AuthenticatedUserResponse>> {
|
||||||
let user = app
|
let user = app
|
||||||
.db
|
.db
|
||||||
.get_user_by_github_account(¶ms.github_login, Some(params.github_user_id))
|
.get_user_by_github_account(¶ms.github_login, params.github_user_id)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| Error::Http(StatusCode::NOT_FOUND, "user not found".into()))?;
|
.ok_or_else(|| Error::Http(StatusCode::NOT_FOUND, "user not found".into()))?;
|
||||||
let metrics_id = app.db.get_user_metrics_id(user.id).await?;
|
let metrics_id = app.db.get_user_metrics_id(user.id).await?;
|
||||||
|
@ -156,7 +156,7 @@ async fn create_user(
|
||||||
Json(params): Json<CreateUserParams>,
|
Json(params): Json<CreateUserParams>,
|
||||||
Extension(app): Extension<Arc<AppState>>,
|
Extension(app): Extension<Arc<AppState>>,
|
||||||
Extension(rpc_server): Extension<Arc<rpc::Server>>,
|
Extension(rpc_server): Extension<Arc<rpc::Server>>,
|
||||||
) -> Result<Json<CreateUserResponse>> {
|
) -> Result<Json<Option<CreateUserResponse>>> {
|
||||||
let user = NewUserParams {
|
let user = NewUserParams {
|
||||||
github_login: params.github_login,
|
github_login: params.github_login,
|
||||||
github_user_id: params.github_user_id,
|
github_user_id: params.github_user_id,
|
||||||
|
@ -165,7 +165,8 @@ async fn create_user(
|
||||||
|
|
||||||
// Creating a user via the normal signup process
|
// Creating a user via the normal signup process
|
||||||
let result = if let Some(email_confirmation_code) = params.email_confirmation_code {
|
let result = if let Some(email_confirmation_code) = params.email_confirmation_code {
|
||||||
app.db
|
if let Some(result) = app
|
||||||
|
.db
|
||||||
.create_user_from_invite(
|
.create_user_from_invite(
|
||||||
&Invite {
|
&Invite {
|
||||||
email_address: params.email_address,
|
email_address: params.email_address,
|
||||||
|
@ -174,6 +175,11 @@ async fn create_user(
|
||||||
user,
|
user,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
|
{
|
||||||
|
result
|
||||||
|
} else {
|
||||||
|
return Ok(Json(None));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Creating a user as an admin
|
// Creating a user as an admin
|
||||||
else if params.admin {
|
else if params.admin {
|
||||||
|
@ -200,11 +206,11 @@ async fn create_user(
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| anyhow!("couldn't find the user we just created"))?;
|
.ok_or_else(|| anyhow!("couldn't find the user we just created"))?;
|
||||||
|
|
||||||
Ok(Json(CreateUserResponse {
|
Ok(Json(Some(CreateUserResponse {
|
||||||
user,
|
user,
|
||||||
metrics_id: result.metrics_id,
|
metrics_id: result.metrics_id,
|
||||||
signup_device_id: result.signup_device_id,
|
signup_device_id: result.signup_device_id,
|
||||||
}))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::sync::Arc;
|
use crate::{
|
||||||
|
db::{self, UserId},
|
||||||
use super::db::{self, UserId};
|
AppState, Error, Result,
|
||||||
use crate::{AppState, Error, Result};
|
};
|
||||||
use anyhow::{anyhow, Context};
|
use anyhow::{anyhow, Context};
|
||||||
use axum::{
|
use axum::{
|
||||||
http::{self, Request, StatusCode},
|
http::{self, Request, StatusCode},
|
||||||
|
@ -13,6 +13,7 @@ use scrypt::{
|
||||||
password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
|
password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
|
||||||
Scrypt,
|
Scrypt,
|
||||||
};
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub async fn validate_header<B>(mut req: Request<B>, next: Next<B>) -> impl IntoResponse {
|
pub async fn validate_header<B>(mut req: Request<B>, next: Next<B>) -> impl IntoResponse {
|
||||||
let mut auth_header = req
|
let mut auth_header = req
|
||||||
|
@ -21,7 +22,7 @@ pub async fn validate_header<B>(mut req: Request<B>, next: Next<B>) -> impl Into
|
||||||
.and_then(|header| header.to_str().ok())
|
.and_then(|header| header.to_str().ok())
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
Error::Http(
|
Error::Http(
|
||||||
StatusCode::BAD_REQUEST,
|
StatusCode::UNAUTHORIZED,
|
||||||
"missing authorization header".to_string(),
|
"missing authorization header".to_string(),
|
||||||
)
|
)
|
||||||
})?
|
})?
|
||||||
|
@ -41,12 +42,18 @@ pub async fn validate_header<B>(mut req: Request<B>, next: Next<B>) -> impl Into
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let state = req.extensions().get::<Arc<AppState>>().unwrap();
|
|
||||||
let mut credentials_valid = false;
|
let mut credentials_valid = false;
|
||||||
for password_hash in state.db.get_access_token_hashes(user_id).await? {
|
let state = req.extensions().get::<Arc<AppState>>().unwrap();
|
||||||
if verify_access_token(access_token, &password_hash)? {
|
if let Some(admin_token) = access_token.strip_prefix("ADMIN_TOKEN:") {
|
||||||
|
if state.config.api_token == admin_token {
|
||||||
credentials_valid = true;
|
credentials_valid = true;
|
||||||
break;
|
}
|
||||||
|
} else {
|
||||||
|
for password_hash in state.db.get_access_token_hashes(user_id).await? {
|
||||||
|
if verify_access_token(access_token, &password_hash)? {
|
||||||
|
credentials_valid = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,7 @@ pub trait Db: Send + Sync {
|
||||||
&self,
|
&self,
|
||||||
invite: &Invite,
|
invite: &Invite,
|
||||||
user: NewUserParams,
|
user: NewUserParams,
|
||||||
) -> Result<NewUserResult>;
|
) -> Result<Option<NewUserResult>>;
|
||||||
|
|
||||||
/// Registers a new project for the given user.
|
/// Registers a new project for the given user.
|
||||||
async fn register_project(&self, host_user_id: UserId) -> Result<ProjectId>;
|
async fn register_project(&self, host_user_id: UserId) -> Result<ProjectId>;
|
||||||
|
@ -482,7 +482,7 @@ impl Db for PostgresDb {
|
||||||
&self,
|
&self,
|
||||||
invite: &Invite,
|
invite: &Invite,
|
||||||
user: NewUserParams,
|
user: NewUserParams,
|
||||||
) -> Result<NewUserResult> {
|
) -> Result<Option<NewUserResult>> {
|
||||||
let mut tx = self.pool.begin().await?;
|
let mut tx = self.pool.begin().await?;
|
||||||
|
|
||||||
let (signup_id, existing_user_id, inviting_user_id, signup_device_id): (
|
let (signup_id, existing_user_id, inviting_user_id, signup_device_id): (
|
||||||
|
@ -506,10 +506,7 @@ impl Db for PostgresDb {
|
||||||
.ok_or_else(|| Error::Http(StatusCode::NOT_FOUND, "no such invite".to_string()))?;
|
.ok_or_else(|| Error::Http(StatusCode::NOT_FOUND, "no such invite".to_string()))?;
|
||||||
|
|
||||||
if existing_user_id.is_some() {
|
if existing_user_id.is_some() {
|
||||||
Err(Error::Http(
|
return Ok(None);
|
||||||
StatusCode::UNPROCESSABLE_ENTITY,
|
|
||||||
"invitation already redeemed".to_string(),
|
|
||||||
))?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let (user_id, metrics_id): (UserId, String) = sqlx::query_as(
|
let (user_id, metrics_id): (UserId, String) = sqlx::query_as(
|
||||||
|
@ -576,12 +573,12 @@ impl Db for PostgresDb {
|
||||||
}
|
}
|
||||||
|
|
||||||
tx.commit().await?;
|
tx.commit().await?;
|
||||||
Ok(NewUserResult {
|
Ok(Some(NewUserResult {
|
||||||
user_id,
|
user_id,
|
||||||
metrics_id,
|
metrics_id,
|
||||||
inviting_user_id,
|
inviting_user_id,
|
||||||
signup_device_id,
|
signup_device_id,
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// invite codes
|
// invite codes
|
||||||
|
@ -1958,7 +1955,7 @@ mod test {
|
||||||
&self,
|
&self,
|
||||||
_invite: &Invite,
|
_invite: &Invite,
|
||||||
_user: NewUserParams,
|
_user: NewUserParams,
|
||||||
) -> Result<NewUserResult> {
|
) -> Result<Option<NewUserResult>> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -852,6 +852,7 @@ async fn test_invite_codes() {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
|
let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
|
||||||
assert_eq!(invite_count, 1);
|
assert_eq!(invite_count, 1);
|
||||||
|
@ -897,6 +898,7 @@ async fn test_invite_codes() {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
|
let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
|
||||||
assert_eq!(invite_count, 0);
|
assert_eq!(invite_count, 0);
|
||||||
|
@ -954,6 +956,7 @@ async fn test_invite_codes() {
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
.unwrap()
|
||||||
.user_id;
|
.user_id;
|
||||||
|
|
||||||
let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
|
let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
|
||||||
|
@ -1099,6 +1102,7 @@ async fn test_signups() {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let user = db.get_user_by_id(user_id).await.unwrap().unwrap();
|
let user = db.get_user_by_id(user_id).await.unwrap().unwrap();
|
||||||
assert!(inviting_user_id.is_none());
|
assert!(inviting_user_id.is_none());
|
||||||
|
@ -1108,19 +1112,21 @@ async fn test_signups() {
|
||||||
assert_eq!(signup_device_id.unwrap(), "device_id_0");
|
assert_eq!(signup_device_id.unwrap(), "device_id_0");
|
||||||
|
|
||||||
// cannot redeem the same signup again.
|
// cannot redeem the same signup again.
|
||||||
db.create_user_from_invite(
|
assert!(db
|
||||||
&Invite {
|
.create_user_from_invite(
|
||||||
email_address: signups_batch1[0].email_address.clone(),
|
&Invite {
|
||||||
email_confirmation_code: signups_batch1[0].email_confirmation_code.clone(),
|
email_address: signups_batch1[0].email_address.clone(),
|
||||||
},
|
email_confirmation_code: signups_batch1[0].email_confirmation_code.clone(),
|
||||||
NewUserParams {
|
},
|
||||||
github_login: "some-other-github_account".into(),
|
NewUserParams {
|
||||||
github_user_id: 1,
|
github_login: "some-other-github_account".into(),
|
||||||
invite_count: 5,
|
github_user_id: 1,
|
||||||
},
|
invite_count: 5,
|
||||||
)
|
},
|
||||||
.await
|
)
|
||||||
.unwrap_err();
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.is_none());
|
||||||
|
|
||||||
// cannot redeem a signup with the wrong confirmation code.
|
// cannot redeem a signup with the wrong confirmation code.
|
||||||
db.create_user_from_invite(
|
db.create_user_from_invite(
|
||||||
|
|
|
@ -6357,8 +6357,7 @@ impl TestServer {
|
||||||
async fn build_app_state(test_db: &TestDb) -> Arc<AppState> {
|
async fn build_app_state(test_db: &TestDb) -> Arc<AppState> {
|
||||||
Arc::new(AppState {
|
Arc::new(AppState {
|
||||||
db: test_db.db().clone(),
|
db: test_db.db().clone(),
|
||||||
api_token: Default::default(),
|
config: Default::default(),
|
||||||
invite_link_prefix: Default::default(),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,25 +28,21 @@ pub struct Config {
|
||||||
pub database_url: String,
|
pub database_url: String,
|
||||||
pub api_token: String,
|
pub api_token: String,
|
||||||
pub invite_link_prefix: String,
|
pub invite_link_prefix: String,
|
||||||
pub honeycomb_api_key: Option<String>,
|
|
||||||
pub honeycomb_dataset: Option<String>,
|
|
||||||
pub rust_log: Option<String>,
|
pub rust_log: Option<String>,
|
||||||
pub log_json: Option<bool>,
|
pub log_json: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
db: Arc<dyn Db>,
|
db: Arc<dyn Db>,
|
||||||
api_token: String,
|
config: Config,
|
||||||
invite_link_prefix: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppState {
|
impl AppState {
|
||||||
async fn new(config: &Config) -> Result<Arc<Self>> {
|
async fn new(config: Config) -> Result<Arc<Self>> {
|
||||||
let db = PostgresDb::new(&config.database_url, 5).await?;
|
let db = PostgresDb::new(&config.database_url, 5).await?;
|
||||||
let this = Self {
|
let this = Self {
|
||||||
db: Arc::new(db),
|
db: Arc::new(db),
|
||||||
api_token: config.api_token.clone(),
|
config,
|
||||||
invite_link_prefix: config.invite_link_prefix.clone(),
|
|
||||||
};
|
};
|
||||||
Ok(Arc::new(this))
|
Ok(Arc::new(this))
|
||||||
}
|
}
|
||||||
|
@ -63,9 +59,9 @@ async fn main() -> Result<()> {
|
||||||
|
|
||||||
let config = envy::from_env::<Config>().expect("error loading config");
|
let config = envy::from_env::<Config>().expect("error loading config");
|
||||||
init_tracing(&config);
|
init_tracing(&config);
|
||||||
let state = AppState::new(&config).await?;
|
let state = AppState::new(config).await?;
|
||||||
|
|
||||||
let listener = TcpListener::bind(&format!("0.0.0.0:{}", config.http_port))
|
let listener = TcpListener::bind(&format!("0.0.0.0:{}", state.config.http_port))
|
||||||
.expect("failed to bind TCP listener");
|
.expect("failed to bind TCP listener");
|
||||||
let rpc_server = rpc::Server::new(state.clone(), None);
|
let rpc_server = rpc::Server::new(state.clone(), None);
|
||||||
|
|
||||||
|
|
|
@ -397,7 +397,7 @@ impl Server {
|
||||||
|
|
||||||
if let Some((code, count)) = invite_code {
|
if let Some((code, count)) = invite_code {
|
||||||
this.peer.send(connection_id, proto::UpdateInviteInfo {
|
this.peer.send(connection_id, proto::UpdateInviteInfo {
|
||||||
url: format!("{}{}", this.app_state.invite_link_prefix, code),
|
url: format!("{}{}", this.app_state.config.invite_link_prefix, code),
|
||||||
count,
|
count,
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
@ -561,7 +561,7 @@ impl Server {
|
||||||
self.peer.send(
|
self.peer.send(
|
||||||
connection_id,
|
connection_id,
|
||||||
proto::UpdateInviteInfo {
|
proto::UpdateInviteInfo {
|
||||||
url: format!("{}{}", self.app_state.invite_link_prefix, &code),
|
url: format!("{}{}", self.app_state.config.invite_link_prefix, &code),
|
||||||
count: user.invite_count as u32,
|
count: user.invite_count as u32,
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
@ -579,7 +579,10 @@ impl Server {
|
||||||
self.peer.send(
|
self.peer.send(
|
||||||
connection_id,
|
connection_id,
|
||||||
proto::UpdateInviteInfo {
|
proto::UpdateInviteInfo {
|
||||||
url: format!("{}{}", self.app_state.invite_link_prefix, invite_code),
|
url: format!(
|
||||||
|
"{}{}",
|
||||||
|
self.app_state.config.invite_link_prefix, invite_code
|
||||||
|
),
|
||||||
count: user.invite_count as u32,
|
count: user.invite_count as u32,
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
use std::{ffi::OsStr, os::unix::prelude::OsStrExt, path::PathBuf, sync::Arc};
|
use std::{ffi::OsStr, fmt::Display, hash::Hash, os::unix::prelude::OsStrExt, path::PathBuf};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use rusqlite::{
|
use collections::HashSet;
|
||||||
named_params, params,
|
use rusqlite::{named_params, params};
|
||||||
types::{FromSql, FromSqlError, FromSqlResult, ValueRef},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::Db;
|
use super::Db;
|
||||||
|
|
||||||
|
@ -31,7 +29,13 @@ pub enum SerializedItemKind {
|
||||||
Diagnostics,
|
Diagnostics,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
impl Display for SerializedItemKind {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str(&format!("{:?}", self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
pub enum SerializedItem {
|
pub enum SerializedItem {
|
||||||
Editor(usize, PathBuf),
|
Editor(usize, PathBuf),
|
||||||
Terminal(usize),
|
Terminal(usize),
|
||||||
|
@ -39,27 +43,6 @@ pub enum SerializedItem {
|
||||||
Diagnostics(usize),
|
Diagnostics(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromSql for SerializedItemKind {
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
match value {
|
|
||||||
ValueRef::Null => Err(FromSqlError::InvalidType),
|
|
||||||
ValueRef::Integer(_) => Err(FromSqlError::InvalidType),
|
|
||||||
ValueRef::Real(_) => Err(FromSqlError::InvalidType),
|
|
||||||
ValueRef::Text(bytes) => {
|
|
||||||
let str = std::str::from_utf8(bytes).map_err(|_| FromSqlError::InvalidType)?;
|
|
||||||
match str {
|
|
||||||
"Editor" => Ok(SerializedItemKind::Editor),
|
|
||||||
"Terminal" => Ok(SerializedItemKind::Terminal),
|
|
||||||
"ProjectSearch" => Ok(SerializedItemKind::ProjectSearch),
|
|
||||||
"Diagnostics" => Ok(SerializedItemKind::Diagnostics),
|
|
||||||
_ => Err(FromSqlError::InvalidType),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ValueRef::Blob(_) => Err(FromSqlError::InvalidType),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SerializedItem {
|
impl SerializedItem {
|
||||||
fn kind(&self) -> SerializedItemKind {
|
fn kind(&self) -> SerializedItemKind {
|
||||||
match self {
|
match self {
|
||||||
|
@ -82,117 +65,206 @@ impl SerializedItem {
|
||||||
|
|
||||||
impl Db {
|
impl Db {
|
||||||
fn write_item(&self, serialized_item: SerializedItem) -> Result<()> {
|
fn write_item(&self, serialized_item: SerializedItem) -> Result<()> {
|
||||||
let mut lock = self.connection.lock();
|
self.real()
|
||||||
let tx = lock.transaction()?;
|
.map(|db| {
|
||||||
|
let mut lock = db.connection.lock();
|
||||||
|
let tx = lock.transaction()?;
|
||||||
|
|
||||||
// Serialize the item
|
// Serialize the item
|
||||||
let id = serialized_item.id();
|
let id = serialized_item.id();
|
||||||
{
|
{
|
||||||
let kind = format!("{:?}", serialized_item.kind());
|
let mut stmt = tx.prepare_cached(
|
||||||
|
"INSERT OR REPLACE INTO items(id, kind) VALUES ((?), (?))",
|
||||||
|
)?;
|
||||||
|
|
||||||
let mut stmt =
|
dbg!("inserting item");
|
||||||
tx.prepare_cached("INSERT OR REPLACE INTO items(id, kind) VALUES ((?), (?))")?;
|
stmt.execute(params![id, serialized_item.kind().to_string()])?;
|
||||||
|
}
|
||||||
|
|
||||||
stmt.execute(params![id, kind])?;
|
// Serialize item data
|
||||||
}
|
match &serialized_item {
|
||||||
|
SerializedItem::Editor(_, path) => {
|
||||||
|
dbg!("inserting path");
|
||||||
|
let mut stmt = tx.prepare_cached(
|
||||||
|
"INSERT OR REPLACE INTO item_path(item_id, path) VALUES ((?), (?))",
|
||||||
|
)?;
|
||||||
|
|
||||||
// Serialize item data
|
let path_bytes = path.as_os_str().as_bytes();
|
||||||
match &serialized_item {
|
stmt.execute(params![id, path_bytes])?;
|
||||||
SerializedItem::Editor(_, path) => {
|
}
|
||||||
let mut stmt = tx.prepare_cached(
|
SerializedItem::ProjectSearch(_, query) => {
|
||||||
"INSERT OR REPLACE INTO item_path(item_id, path) VALUES ((?), (?))",
|
dbg!("inserting query");
|
||||||
)?;
|
let mut stmt = tx.prepare_cached(
|
||||||
|
"INSERT OR REPLACE INTO item_query(item_id, query) VALUES ((?), (?))",
|
||||||
|
)?;
|
||||||
|
|
||||||
let path_bytes = path.as_os_str().as_bytes();
|
stmt.execute(params![id, query])?;
|
||||||
stmt.execute(params![id, path_bytes])?;
|
}
|
||||||
}
|
_ => {}
|
||||||
SerializedItem::ProjectSearch(_, query) => {
|
}
|
||||||
let mut stmt = tx.prepare_cached(
|
|
||||||
"INSERT OR REPLACE INTO item_query(item_id, query) VALUES ((?), (?))",
|
|
||||||
)?;
|
|
||||||
|
|
||||||
stmt.execute(params![id, query])?;
|
tx.commit()?;
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
tx.commit()?;
|
let mut stmt = lock.prepare_cached("SELECT id, kind FROM items")?;
|
||||||
|
let _ = stmt
|
||||||
|
.query_map([], |row| {
|
||||||
|
let zero: usize = row.get(0)?;
|
||||||
|
let one: String = row.get(1)?;
|
||||||
|
|
||||||
Ok(())
|
dbg!(zero, one);
|
||||||
|
Ok(())
|
||||||
|
})?
|
||||||
|
.collect::<Vec<Result<(), _>>>();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.unwrap_or(Ok(()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete_item(&self, item_id: usize) -> Result<()> {
|
fn delete_item(&self, item_id: usize) -> Result<()> {
|
||||||
let lock = self.connection.lock();
|
self.real()
|
||||||
|
.map(|db| {
|
||||||
|
let lock = db.connection.lock();
|
||||||
|
|
||||||
let mut stmt = lock.prepare_cached(
|
let mut stmt = lock.prepare_cached(
|
||||||
"
|
r#"
|
||||||
DELETE FROM items WHERE id = (:id);
|
DELETE FROM items WHERE id = (:id);
|
||||||
DELETE FROM item_path WHERE id = (:id);
|
DELETE FROM item_path WHERE id = (:id);
|
||||||
DELETE FROM item_query WHERE id = (:id);
|
DELETE FROM item_query WHERE id = (:id);
|
||||||
",
|
"#,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
stmt.execute(named_params! {":id": item_id})?;
|
stmt.execute(named_params! {":id": item_id})?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
})
|
||||||
|
.unwrap_or(Ok(()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn take_items(&self) -> Result<Vec<SerializedItem>> {
|
fn take_items(&self) -> Result<HashSet<SerializedItem>> {
|
||||||
let mut lock = self.connection.lock();
|
self.real()
|
||||||
|
.map(|db| {
|
||||||
|
let mut lock = db.connection.lock();
|
||||||
|
|
||||||
let tx = lock.transaction()?;
|
let tx = lock.transaction()?;
|
||||||
|
|
||||||
// When working with transactions in rusqlite, need to make this kind of scope
|
// When working with transactions in rusqlite, need to make this kind of scope
|
||||||
// To make the borrow stuff work correctly. Don't know why, rust is wild.
|
// To make the borrow stuff work correctly. Don't know why, rust is wild.
|
||||||
let result = {
|
let result = {
|
||||||
let mut read_stmt = tx.prepare_cached(
|
let mut editors_stmt = tx.prepare_cached(
|
||||||
"
|
r#"
|
||||||
SELECT items.id, items.kind, item_path.path, item_query.query
|
SELECT items.id, item_path.path
|
||||||
FROM items
|
FROM items
|
||||||
LEFT JOIN item_path
|
LEFT JOIN item_path
|
||||||
ON items.id = item_path.item_id
|
ON items.id = item_path.item_id
|
||||||
LEFT JOIN item_query
|
WHERE items.kind = ?;
|
||||||
ON items.id = item_query.item_id
|
"#,
|
||||||
ORDER BY items.id
|
)?;
|
||||||
",
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let result = read_stmt
|
let editors_iter = editors_stmt.query_map(
|
||||||
.query_map([], |row| {
|
[SerializedItemKind::Editor.to_string()],
|
||||||
let id: usize = row.get(0)?;
|
|row| {
|
||||||
let kind: SerializedItemKind = row.get(1)?;
|
let id: usize = row.get(0)?;
|
||||||
|
|
||||||
match kind {
|
let buf: Vec<u8> = row.get(1)?;
|
||||||
SerializedItemKind::Editor => {
|
|
||||||
let buf: Vec<u8> = row.get(2)?;
|
|
||||||
let path: PathBuf = OsStr::from_bytes(&buf).into();
|
let path: PathBuf = OsStr::from_bytes(&buf).into();
|
||||||
|
|
||||||
Ok(SerializedItem::Editor(id, path))
|
Ok(SerializedItem::Editor(id, path))
|
||||||
}
|
},
|
||||||
SerializedItemKind::Terminal => Ok(SerializedItem::Terminal(id)),
|
)?;
|
||||||
SerializedItemKind::ProjectSearch => {
|
|
||||||
let query: Arc<str> = row.get(3)?;
|
|
||||||
Ok(SerializedItem::ProjectSearch(id, query.to_string()))
|
|
||||||
}
|
|
||||||
SerializedItemKind::Diagnostics => Ok(SerializedItem::Diagnostics(id)),
|
|
||||||
}
|
|
||||||
})?
|
|
||||||
.collect::<Result<Vec<SerializedItem>, rusqlite::Error>>()?;
|
|
||||||
|
|
||||||
let mut delete_stmt = tx.prepare_cached(
|
let mut terminals_stmt = tx.prepare_cached(
|
||||||
"DELETE FROM items;
|
r#"
|
||||||
DELETE FROM item_path;
|
SELECT items.id
|
||||||
DELETE FROM item_query;",
|
FROM items
|
||||||
)?;
|
WHERE items.kind = ?;
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
let terminals_iter = terminals_stmt.query_map(
|
||||||
|
[SerializedItemKind::Terminal.to_string()],
|
||||||
|
|row| {
|
||||||
|
let id: usize = row.get(0)?;
|
||||||
|
|
||||||
delete_stmt.execute([])?;
|
Ok(SerializedItem::Terminal(id))
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
result
|
let mut search_stmt = tx.prepare_cached(
|
||||||
};
|
r#"
|
||||||
|
SELECT items.id, item_query.query
|
||||||
|
FROM items
|
||||||
|
LEFT JOIN item_query
|
||||||
|
ON items.id = item_query.item_id
|
||||||
|
WHERE items.kind = ?;
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
let searches_iter = search_stmt.query_map(
|
||||||
|
[SerializedItemKind::ProjectSearch.to_string()],
|
||||||
|
|row| {
|
||||||
|
let id: usize = row.get(0)?;
|
||||||
|
let query = row.get(1)?;
|
||||||
|
|
||||||
tx.commit()?;
|
Ok(SerializedItem::ProjectSearch(id, query))
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(result)
|
#[cfg(debug_assertions)]
|
||||||
|
let tmp =
|
||||||
|
searches_iter.collect::<Vec<Result<SerializedItem, rusqlite::Error>>>();
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
debug_assert!(tmp.len() == 0 || tmp.len() == 1);
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
let searches_iter = tmp.into_iter();
|
||||||
|
|
||||||
|
let mut diagnostic_stmt = tx.prepare_cached(
|
||||||
|
r#"
|
||||||
|
SELECT items.id
|
||||||
|
FROM items
|
||||||
|
WHERE items.kind = ?;
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let diagnostics_iter = diagnostic_stmt.query_map(
|
||||||
|
[SerializedItemKind::Diagnostics.to_string()],
|
||||||
|
|row| {
|
||||||
|
let id: usize = row.get(0)?;
|
||||||
|
|
||||||
|
Ok(SerializedItem::Diagnostics(id))
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
let tmp =
|
||||||
|
diagnostics_iter.collect::<Vec<Result<SerializedItem, rusqlite::Error>>>();
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
debug_assert!(tmp.len() == 0 || tmp.len() == 1);
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
let diagnostics_iter = tmp.into_iter();
|
||||||
|
|
||||||
|
let res = editors_iter
|
||||||
|
.chain(terminals_iter)
|
||||||
|
.chain(diagnostics_iter)
|
||||||
|
.chain(searches_iter)
|
||||||
|
.collect::<Result<HashSet<SerializedItem>, rusqlite::Error>>()?;
|
||||||
|
|
||||||
|
let mut delete_stmt = tx.prepare_cached(
|
||||||
|
r#"
|
||||||
|
DELETE FROM items;
|
||||||
|
DELETE FROM item_path;
|
||||||
|
DELETE FROM item_query;
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
delete_stmt.execute([])?;
|
||||||
|
|
||||||
|
res
|
||||||
|
};
|
||||||
|
|
||||||
|
tx.commit()?;
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
})
|
||||||
|
.unwrap_or(Ok(HashSet::default()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,29 +276,32 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_items_round_trip() -> Result<()> {
|
fn test_items_round_trip() -> Result<()> {
|
||||||
let db = Db::open_in_memory()?;
|
let db = Db::open_in_memory();
|
||||||
|
|
||||||
let mut items = vec![
|
let mut items = vec![
|
||||||
SerializedItem::Editor(0, PathBuf::from("/tmp/test.txt")),
|
SerializedItem::Editor(0, PathBuf::from("/tmp/test.txt")),
|
||||||
SerializedItem::Terminal(1),
|
SerializedItem::Terminal(1),
|
||||||
SerializedItem::ProjectSearch(2, "Test query!".to_string()),
|
SerializedItem::ProjectSearch(2, "Test query!".to_string()),
|
||||||
SerializedItem::Diagnostics(3),
|
SerializedItem::Diagnostics(3),
|
||||||
];
|
]
|
||||||
|
.into_iter()
|
||||||
|
.collect::<HashSet<_>>();
|
||||||
|
|
||||||
for item in items.iter() {
|
for item in items.iter() {
|
||||||
|
dbg!("Inserting... ");
|
||||||
db.write_item(item.clone())?;
|
db.write_item(item.clone())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!(items, db.take_items()?);
|
assert_eq!(items, db.take_items()?);
|
||||||
|
|
||||||
// Check that it's empty, as expected
|
// Check that it's empty, as expected
|
||||||
assert_eq!(Vec::<SerializedItem>::new(), db.take_items()?);
|
assert_eq!(HashSet::default(), db.take_items()?);
|
||||||
|
|
||||||
for item in items.iter() {
|
for item in items.iter() {
|
||||||
db.write_item(item.clone())?;
|
db.write_item(item.clone())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
items.remove(2);
|
items.remove(&SerializedItem::ProjectSearch(2, "Test query!".to_string()));
|
||||||
db.delete_item(2)?;
|
db.delete_item(2)?;
|
||||||
|
|
||||||
assert_eq!(items, db.take_items()?);
|
assert_eq!(items, db.take_items()?);
|
||||||
|
|
|
@ -35,7 +35,6 @@ serde = { version = "1.0", features = ["derive"] }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
gpui = { path = "../gpui", features = ["test-support"] }
|
gpui = { path = "../gpui", features = ["test-support"] }
|
||||||
client = { path = "../client", features = ["test-support"]}
|
client = { path = "../client", features = ["test-support"]}
|
||||||
|
|
|
@ -1,77 +1,71 @@
|
||||||
use alacritty_terminal::{ansi::Color as AnsiColor, term::color::Rgb as AlacRgb};
|
use alacritty_terminal::{ansi::Color as AnsiColor, term::color::Rgb as AlacRgb};
|
||||||
use gpui::color::Color;
|
use gpui::color::Color;
|
||||||
use theme::TerminalColors;
|
use theme::TerminalStyle;
|
||||||
|
|
||||||
///Converts a 2, 8, or 24 bit color ANSI color to the GPUI equivalent
|
///Converts a 2, 8, or 24 bit color ANSI color to the GPUI equivalent
|
||||||
pub fn convert_color(alac_color: &AnsiColor, colors: &TerminalColors, modal: bool) -> Color {
|
pub fn convert_color(alac_color: &AnsiColor, style: &TerminalStyle) -> Color {
|
||||||
let background = if modal {
|
|
||||||
colors.modal_background
|
|
||||||
} else {
|
|
||||||
colors.background
|
|
||||||
};
|
|
||||||
|
|
||||||
match alac_color {
|
match alac_color {
|
||||||
//Named and theme defined colors
|
//Named and theme defined colors
|
||||||
alacritty_terminal::ansi::Color::Named(n) => match n {
|
alacritty_terminal::ansi::Color::Named(n) => match n {
|
||||||
alacritty_terminal::ansi::NamedColor::Black => colors.black,
|
alacritty_terminal::ansi::NamedColor::Black => style.black,
|
||||||
alacritty_terminal::ansi::NamedColor::Red => colors.red,
|
alacritty_terminal::ansi::NamedColor::Red => style.red,
|
||||||
alacritty_terminal::ansi::NamedColor::Green => colors.green,
|
alacritty_terminal::ansi::NamedColor::Green => style.green,
|
||||||
alacritty_terminal::ansi::NamedColor::Yellow => colors.yellow,
|
alacritty_terminal::ansi::NamedColor::Yellow => style.yellow,
|
||||||
alacritty_terminal::ansi::NamedColor::Blue => colors.blue,
|
alacritty_terminal::ansi::NamedColor::Blue => style.blue,
|
||||||
alacritty_terminal::ansi::NamedColor::Magenta => colors.magenta,
|
alacritty_terminal::ansi::NamedColor::Magenta => style.magenta,
|
||||||
alacritty_terminal::ansi::NamedColor::Cyan => colors.cyan,
|
alacritty_terminal::ansi::NamedColor::Cyan => style.cyan,
|
||||||
alacritty_terminal::ansi::NamedColor::White => colors.white,
|
alacritty_terminal::ansi::NamedColor::White => style.white,
|
||||||
alacritty_terminal::ansi::NamedColor::BrightBlack => colors.bright_black,
|
alacritty_terminal::ansi::NamedColor::BrightBlack => style.bright_black,
|
||||||
alacritty_terminal::ansi::NamedColor::BrightRed => colors.bright_red,
|
alacritty_terminal::ansi::NamedColor::BrightRed => style.bright_red,
|
||||||
alacritty_terminal::ansi::NamedColor::BrightGreen => colors.bright_green,
|
alacritty_terminal::ansi::NamedColor::BrightGreen => style.bright_green,
|
||||||
alacritty_terminal::ansi::NamedColor::BrightYellow => colors.bright_yellow,
|
alacritty_terminal::ansi::NamedColor::BrightYellow => style.bright_yellow,
|
||||||
alacritty_terminal::ansi::NamedColor::BrightBlue => colors.bright_blue,
|
alacritty_terminal::ansi::NamedColor::BrightBlue => style.bright_blue,
|
||||||
alacritty_terminal::ansi::NamedColor::BrightMagenta => colors.bright_magenta,
|
alacritty_terminal::ansi::NamedColor::BrightMagenta => style.bright_magenta,
|
||||||
alacritty_terminal::ansi::NamedColor::BrightCyan => colors.bright_cyan,
|
alacritty_terminal::ansi::NamedColor::BrightCyan => style.bright_cyan,
|
||||||
alacritty_terminal::ansi::NamedColor::BrightWhite => colors.bright_white,
|
alacritty_terminal::ansi::NamedColor::BrightWhite => style.bright_white,
|
||||||
alacritty_terminal::ansi::NamedColor::Foreground => colors.foreground,
|
alacritty_terminal::ansi::NamedColor::Foreground => style.foreground,
|
||||||
alacritty_terminal::ansi::NamedColor::Background => background,
|
alacritty_terminal::ansi::NamedColor::Background => style.background,
|
||||||
alacritty_terminal::ansi::NamedColor::Cursor => colors.cursor,
|
alacritty_terminal::ansi::NamedColor::Cursor => style.cursor,
|
||||||
alacritty_terminal::ansi::NamedColor::DimBlack => colors.dim_black,
|
alacritty_terminal::ansi::NamedColor::DimBlack => style.dim_black,
|
||||||
alacritty_terminal::ansi::NamedColor::DimRed => colors.dim_red,
|
alacritty_terminal::ansi::NamedColor::DimRed => style.dim_red,
|
||||||
alacritty_terminal::ansi::NamedColor::DimGreen => colors.dim_green,
|
alacritty_terminal::ansi::NamedColor::DimGreen => style.dim_green,
|
||||||
alacritty_terminal::ansi::NamedColor::DimYellow => colors.dim_yellow,
|
alacritty_terminal::ansi::NamedColor::DimYellow => style.dim_yellow,
|
||||||
alacritty_terminal::ansi::NamedColor::DimBlue => colors.dim_blue,
|
alacritty_terminal::ansi::NamedColor::DimBlue => style.dim_blue,
|
||||||
alacritty_terminal::ansi::NamedColor::DimMagenta => colors.dim_magenta,
|
alacritty_terminal::ansi::NamedColor::DimMagenta => style.dim_magenta,
|
||||||
alacritty_terminal::ansi::NamedColor::DimCyan => colors.dim_cyan,
|
alacritty_terminal::ansi::NamedColor::DimCyan => style.dim_cyan,
|
||||||
alacritty_terminal::ansi::NamedColor::DimWhite => colors.dim_white,
|
alacritty_terminal::ansi::NamedColor::DimWhite => style.dim_white,
|
||||||
alacritty_terminal::ansi::NamedColor::BrightForeground => colors.bright_foreground,
|
alacritty_terminal::ansi::NamedColor::BrightForeground => style.bright_foreground,
|
||||||
alacritty_terminal::ansi::NamedColor::DimForeground => colors.dim_foreground,
|
alacritty_terminal::ansi::NamedColor::DimForeground => style.dim_foreground,
|
||||||
},
|
},
|
||||||
//'True' colors
|
//'True' colors
|
||||||
alacritty_terminal::ansi::Color::Spec(rgb) => Color::new(rgb.r, rgb.g, rgb.b, u8::MAX),
|
alacritty_terminal::ansi::Color::Spec(rgb) => Color::new(rgb.r, rgb.g, rgb.b, u8::MAX),
|
||||||
//8 bit, indexed colors
|
//8 bit, indexed colors
|
||||||
alacritty_terminal::ansi::Color::Indexed(i) => get_color_at_index(&(*i as usize), colors),
|
alacritty_terminal::ansi::Color::Indexed(i) => get_color_at_index(&(*i as usize), style),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///Converts an 8 bit ANSI color to it's GPUI equivalent.
|
///Converts an 8 bit ANSI color to it's GPUI equivalent.
|
||||||
///Accepts usize for compatability with the alacritty::Colors interface,
|
///Accepts usize for compatability with the alacritty::Colors interface,
|
||||||
///Other than that use case, should only be called with values in the [0,255] range
|
///Other than that use case, should only be called with values in the [0,255] range
|
||||||
pub fn get_color_at_index(index: &usize, colors: &TerminalColors) -> Color {
|
pub fn get_color_at_index(index: &usize, style: &TerminalStyle) -> Color {
|
||||||
match index {
|
match index {
|
||||||
//0-15 are the same as the named colors above
|
//0-15 are the same as the named colors above
|
||||||
0 => colors.black,
|
0 => style.black,
|
||||||
1 => colors.red,
|
1 => style.red,
|
||||||
2 => colors.green,
|
2 => style.green,
|
||||||
3 => colors.yellow,
|
3 => style.yellow,
|
||||||
4 => colors.blue,
|
4 => style.blue,
|
||||||
5 => colors.magenta,
|
5 => style.magenta,
|
||||||
6 => colors.cyan,
|
6 => style.cyan,
|
||||||
7 => colors.white,
|
7 => style.white,
|
||||||
8 => colors.bright_black,
|
8 => style.bright_black,
|
||||||
9 => colors.bright_red,
|
9 => style.bright_red,
|
||||||
10 => colors.bright_green,
|
10 => style.bright_green,
|
||||||
11 => colors.bright_yellow,
|
11 => style.bright_yellow,
|
||||||
12 => colors.bright_blue,
|
12 => style.bright_blue,
|
||||||
13 => colors.bright_magenta,
|
13 => style.bright_magenta,
|
||||||
14 => colors.bright_cyan,
|
14 => style.bright_cyan,
|
||||||
15 => colors.bright_white,
|
15 => style.bright_white,
|
||||||
//16-231 are mapped to their RGB colors on a 0-5 range per channel
|
//16-231 are mapped to their RGB colors on a 0-5 range per channel
|
||||||
16..=231 => {
|
16..=231 => {
|
||||||
let (r, g, b) = rgb_for_index(&(*index as u8)); //Split the index into it's ANSI-RGB components
|
let (r, g, b) = rgb_for_index(&(*index as u8)); //Split the index into it's ANSI-RGB components
|
||||||
|
@ -85,19 +79,19 @@ pub fn get_color_at_index(index: &usize, colors: &TerminalColors) -> Color {
|
||||||
Color::new(i * step, i * step, i * step, u8::MAX) //Map the ANSI-grayscale components to the RGB-grayscale
|
Color::new(i * step, i * step, i * step, u8::MAX) //Map the ANSI-grayscale components to the RGB-grayscale
|
||||||
}
|
}
|
||||||
//For compatability with the alacritty::Colors interface
|
//For compatability with the alacritty::Colors interface
|
||||||
256 => colors.foreground,
|
256 => style.foreground,
|
||||||
257 => colors.background,
|
257 => style.background,
|
||||||
258 => colors.cursor,
|
258 => style.cursor,
|
||||||
259 => colors.dim_black,
|
259 => style.dim_black,
|
||||||
260 => colors.dim_red,
|
260 => style.dim_red,
|
||||||
261 => colors.dim_green,
|
261 => style.dim_green,
|
||||||
262 => colors.dim_yellow,
|
262 => style.dim_yellow,
|
||||||
263 => colors.dim_blue,
|
263 => style.dim_blue,
|
||||||
264 => colors.dim_magenta,
|
264 => style.dim_magenta,
|
||||||
265 => colors.dim_cyan,
|
265 => style.dim_cyan,
|
||||||
266 => colors.dim_white,
|
266 => style.dim_white,
|
||||||
267 => colors.bright_foreground,
|
267 => style.bright_foreground,
|
||||||
268 => colors.black, //'Dim Background', non-standard color
|
268 => style.black, //'Dim Background', non-standard color
|
||||||
_ => Color::new(0, 0, 0, 255),
|
_ => Color::new(0, 0, 0, 255),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -603,7 +603,7 @@ impl Terminal {
|
||||||
InternalEvent::ColorRequest(index, format) => {
|
InternalEvent::ColorRequest(index, format) => {
|
||||||
let color = term.colors()[*index].unwrap_or_else(|| {
|
let color = term.colors()[*index].unwrap_or_else(|| {
|
||||||
let term_style = &cx.global::<Settings>().theme.terminal;
|
let term_style = &cx.global::<Settings>().theme.terminal;
|
||||||
to_alac_rgb(get_color_at_index(index, &term_style.colors))
|
to_alac_rgb(get_color_at_index(index, &term_style))
|
||||||
});
|
});
|
||||||
self.write_to_pty(format(color))
|
self.write_to_pty(format(color))
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,6 @@ impl TerminalContainerContent {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TerminalContainer {
|
pub struct TerminalContainer {
|
||||||
modal: bool,
|
|
||||||
pub content: TerminalContainerContent,
|
pub content: TerminalContainerContent,
|
||||||
associated_directory: Option<PathBuf>,
|
associated_directory: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
@ -128,7 +127,6 @@ impl TerminalContainer {
|
||||||
cx.focus(content.handle());
|
cx.focus(content.handle());
|
||||||
|
|
||||||
TerminalContainer {
|
TerminalContainer {
|
||||||
modal,
|
|
||||||
content,
|
content,
|
||||||
associated_directory: working_directory,
|
associated_directory: working_directory,
|
||||||
}
|
}
|
||||||
|
@ -141,7 +139,6 @@ impl TerminalContainer {
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let connected_view = cx.add_view(|cx| TerminalView::from_terminal(terminal, modal, cx));
|
let connected_view = cx.add_view(|cx| TerminalView::from_terminal(terminal, modal, cx));
|
||||||
TerminalContainer {
|
TerminalContainer {
|
||||||
modal,
|
|
||||||
content: TerminalContainerContent::Connected(connected_view),
|
content: TerminalContainerContent::Connected(connected_view),
|
||||||
associated_directory: None,
|
associated_directory: None,
|
||||||
}
|
}
|
||||||
|
@ -161,17 +158,11 @@ impl View for TerminalContainer {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox {
|
fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox {
|
||||||
let child_view = match &self.content {
|
match &self.content {
|
||||||
TerminalContainerContent::Connected(connected) => ChildView::new(connected, cx),
|
TerminalContainerContent::Connected(connected) => ChildView::new(connected, cx),
|
||||||
TerminalContainerContent::Error(error) => ChildView::new(error, cx),
|
TerminalContainerContent::Error(error) => ChildView::new(error, cx),
|
||||||
};
|
|
||||||
if self.modal {
|
|
||||||
let settings = cx.global::<Settings>();
|
|
||||||
let container_style = settings.theme.terminal.modal_container;
|
|
||||||
child_view.contained().with_style(container_style).boxed()
|
|
||||||
} else {
|
|
||||||
child_view.boxed()
|
|
||||||
}
|
}
|
||||||
|
.boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
|
fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
|
||||||
|
@ -179,14 +170,6 @@ impl View for TerminalContainer {
|
||||||
cx.focus(self.content.handle());
|
cx.focus(self.content.handle());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn keymap_context(&self, _: &gpui::AppContext) -> gpui::keymap::Context {
|
|
||||||
let mut context = Self::default_keymap_context();
|
|
||||||
if self.modal {
|
|
||||||
context.set.insert("ModalTerminal".into());
|
|
||||||
}
|
|
||||||
context
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl View for ErrorView {
|
impl View for ErrorView {
|
||||||
|
|
|
@ -152,7 +152,6 @@ impl LayoutRect {
|
||||||
pub struct TerminalElement {
|
pub struct TerminalElement {
|
||||||
terminal: WeakModelHandle<Terminal>,
|
terminal: WeakModelHandle<Terminal>,
|
||||||
view: WeakViewHandle<TerminalView>,
|
view: WeakViewHandle<TerminalView>,
|
||||||
modal: bool,
|
|
||||||
focused: bool,
|
focused: bool,
|
||||||
cursor_visible: bool,
|
cursor_visible: bool,
|
||||||
}
|
}
|
||||||
|
@ -161,14 +160,12 @@ impl TerminalElement {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
view: WeakViewHandle<TerminalView>,
|
view: WeakViewHandle<TerminalView>,
|
||||||
terminal: WeakModelHandle<Terminal>,
|
terminal: WeakModelHandle<Terminal>,
|
||||||
modal: bool,
|
|
||||||
focused: bool,
|
focused: bool,
|
||||||
cursor_visible: bool,
|
cursor_visible: bool,
|
||||||
) -> TerminalElement {
|
) -> TerminalElement {
|
||||||
TerminalElement {
|
TerminalElement {
|
||||||
view,
|
view,
|
||||||
terminal,
|
terminal,
|
||||||
modal,
|
|
||||||
focused,
|
focused,
|
||||||
cursor_visible,
|
cursor_visible,
|
||||||
}
|
}
|
||||||
|
@ -182,7 +179,6 @@ impl TerminalElement {
|
||||||
terminal_theme: &TerminalStyle,
|
terminal_theme: &TerminalStyle,
|
||||||
text_layout_cache: &TextLayoutCache,
|
text_layout_cache: &TextLayoutCache,
|
||||||
font_cache: &FontCache,
|
font_cache: &FontCache,
|
||||||
modal: bool,
|
|
||||||
hyperlink: Option<(HighlightStyle, &RangeInclusive<Point>)>,
|
hyperlink: Option<(HighlightStyle, &RangeInclusive<Point>)>,
|
||||||
) -> (Vec<LayoutCell>, Vec<LayoutRect>) {
|
) -> (Vec<LayoutCell>, Vec<LayoutRect>) {
|
||||||
let mut cells = vec![];
|
let mut cells = vec![];
|
||||||
|
@ -222,7 +218,7 @@ impl TerminalElement {
|
||||||
cur_rect = Some(LayoutRect::new(
|
cur_rect = Some(LayoutRect::new(
|
||||||
Point::new(line_index as i32, cell.point.column.0 as i32),
|
Point::new(line_index as i32, cell.point.column.0 as i32),
|
||||||
1,
|
1,
|
||||||
convert_color(&bg, &terminal_theme.colors, modal),
|
convert_color(&bg, &terminal_theme),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -231,7 +227,7 @@ impl TerminalElement {
|
||||||
cur_rect = Some(LayoutRect::new(
|
cur_rect = Some(LayoutRect::new(
|
||||||
Point::new(line_index as i32, cell.point.column.0 as i32),
|
Point::new(line_index as i32, cell.point.column.0 as i32),
|
||||||
1,
|
1,
|
||||||
convert_color(&bg, &terminal_theme.colors, modal),
|
convert_color(&bg, &terminal_theme),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -248,7 +244,6 @@ impl TerminalElement {
|
||||||
terminal_theme,
|
terminal_theme,
|
||||||
text_style,
|
text_style,
|
||||||
font_cache,
|
font_cache,
|
||||||
modal,
|
|
||||||
hyperlink,
|
hyperlink,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -308,11 +303,10 @@ impl TerminalElement {
|
||||||
style: &TerminalStyle,
|
style: &TerminalStyle,
|
||||||
text_style: &TextStyle,
|
text_style: &TextStyle,
|
||||||
font_cache: &FontCache,
|
font_cache: &FontCache,
|
||||||
modal: bool,
|
|
||||||
hyperlink: Option<(HighlightStyle, &RangeInclusive<Point>)>,
|
hyperlink: Option<(HighlightStyle, &RangeInclusive<Point>)>,
|
||||||
) -> RunStyle {
|
) -> RunStyle {
|
||||||
let flags = indexed.cell.flags;
|
let flags = indexed.cell.flags;
|
||||||
let fg = convert_color(&fg, &style.colors, modal);
|
let fg = convert_color(&fg, &style);
|
||||||
|
|
||||||
let mut underline = flags
|
let mut underline = flags
|
||||||
.intersects(Flags::ALL_UNDERLINES)
|
.intersects(Flags::ALL_UNDERLINES)
|
||||||
|
@ -574,11 +568,7 @@ impl Element for TerminalElement {
|
||||||
Default::default()
|
Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let background_color = if self.modal {
|
let background_color = terminal_theme.background;
|
||||||
terminal_theme.colors.modal_background
|
|
||||||
} else {
|
|
||||||
terminal_theme.colors.background
|
|
||||||
};
|
|
||||||
let terminal_handle = self.terminal.upgrade(cx).unwrap();
|
let terminal_handle = self.terminal.upgrade(cx).unwrap();
|
||||||
|
|
||||||
let last_hovered_hyperlink = terminal_handle.update(cx.app, |terminal, cx| {
|
let last_hovered_hyperlink = terminal_handle.update(cx.app, |terminal, cx| {
|
||||||
|
@ -639,7 +629,6 @@ impl Element for TerminalElement {
|
||||||
&terminal_theme,
|
&terminal_theme,
|
||||||
cx.text_layout_cache,
|
cx.text_layout_cache,
|
||||||
cx.font_cache(),
|
cx.font_cache(),
|
||||||
self.modal,
|
|
||||||
last_hovered_hyperlink
|
last_hovered_hyperlink
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|(_, range, _)| (link_style, range)),
|
.map(|(_, range, _)| (link_style, range)),
|
||||||
|
@ -655,9 +644,9 @@ impl Element for TerminalElement {
|
||||||
let str_trxt = cursor_char.to_string();
|
let str_trxt = cursor_char.to_string();
|
||||||
|
|
||||||
let color = if self.focused {
|
let color = if self.focused {
|
||||||
terminal_theme.colors.background
|
terminal_theme.background
|
||||||
} else {
|
} else {
|
||||||
terminal_theme.colors.foreground
|
terminal_theme.foreground
|
||||||
};
|
};
|
||||||
|
|
||||||
cx.text_layout_cache.layout_str(
|
cx.text_layout_cache.layout_str(
|
||||||
|
@ -691,7 +680,7 @@ impl Element for TerminalElement {
|
||||||
cursor_position,
|
cursor_position,
|
||||||
block_width,
|
block_width,
|
||||||
dimensions.line_height,
|
dimensions.line_height,
|
||||||
terminal_theme.colors.cursor,
|
terminal_theme.cursor,
|
||||||
shape,
|
shape,
|
||||||
text,
|
text,
|
||||||
)
|
)
|
||||||
|
|
|
@ -321,7 +321,6 @@ impl View for TerminalView {
|
||||||
TerminalElement::new(
|
TerminalElement::new(
|
||||||
cx.handle(),
|
cx.handle(),
|
||||||
terminal_handle,
|
terminal_handle,
|
||||||
self.modal,
|
|
||||||
focused,
|
focused,
|
||||||
self.should_show_cursor(focused, cx),
|
self.should_show_cursor(focused, cx),
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,7 +2,7 @@ mod theme_registry;
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
color::Color,
|
color::Color,
|
||||||
elements::{ContainerStyle, ImageStyle, LabelStyle, TooltipStyle},
|
elements::{ContainerStyle, ImageStyle, LabelStyle, Shadow, TooltipStyle},
|
||||||
fonts::{HighlightStyle, TextStyle},
|
fonts::{HighlightStyle, TextStyle},
|
||||||
Border, MouseState,
|
Border, MouseState,
|
||||||
};
|
};
|
||||||
|
@ -18,7 +18,6 @@ pub struct Theme {
|
||||||
pub meta: ThemeMeta,
|
pub meta: ThemeMeta,
|
||||||
pub workspace: Workspace,
|
pub workspace: Workspace,
|
||||||
pub context_menu: ContextMenu,
|
pub context_menu: ContextMenu,
|
||||||
pub chat_panel: ChatPanel,
|
|
||||||
pub contacts_popover: ContactsPopover,
|
pub contacts_popover: ContactsPopover,
|
||||||
pub contact_list: ContactList,
|
pub contact_list: ContactList,
|
||||||
pub contact_finder: ContactFinder,
|
pub contact_finder: ContactFinder,
|
||||||
|
@ -35,6 +34,7 @@ pub struct Theme {
|
||||||
pub incoming_call_notification: IncomingCallNotification,
|
pub incoming_call_notification: IncomingCallNotification,
|
||||||
pub tooltip: TooltipStyle,
|
pub tooltip: TooltipStyle,
|
||||||
pub terminal: TerminalStyle,
|
pub terminal: TerminalStyle,
|
||||||
|
pub color_scheme: ColorScheme,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Default, Clone)]
|
#[derive(Deserialize, Default, Clone)]
|
||||||
|
@ -318,18 +318,6 @@ pub struct SidebarItem {
|
||||||
pub icon_size: f32,
|
pub icon_size: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Default)]
|
|
||||||
pub struct ChatPanel {
|
|
||||||
#[serde(flatten)]
|
|
||||||
pub container: ContainerStyle,
|
|
||||||
pub message: ChatMessage,
|
|
||||||
pub pending_message: ChatMessage,
|
|
||||||
pub channel_select: ChannelSelect,
|
|
||||||
pub input_editor: FieldEditor,
|
|
||||||
pub sign_in_prompt: TextStyle,
|
|
||||||
pub hovered_sign_in_prompt: TextStyle,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Default)]
|
#[derive(Deserialize, Default)]
|
||||||
pub struct ProjectPanel {
|
pub struct ProjectPanel {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
|
@ -772,12 +760,6 @@ pub struct HoverPopover {
|
||||||
|
|
||||||
#[derive(Clone, Deserialize, Default)]
|
#[derive(Clone, Deserialize, Default)]
|
||||||
pub struct TerminalStyle {
|
pub struct TerminalStyle {
|
||||||
pub colors: TerminalColors,
|
|
||||||
pub modal_container: ContainerStyle,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Deserialize, Default)]
|
|
||||||
pub struct TerminalColors {
|
|
||||||
pub black: Color,
|
pub black: Color,
|
||||||
pub red: Color,
|
pub red: Color,
|
||||||
pub green: Color,
|
pub green: Color,
|
||||||
|
@ -809,3 +791,67 @@ pub struct TerminalColors {
|
||||||
pub bright_foreground: Color,
|
pub bright_foreground: Color,
|
||||||
pub dim_foreground: Color,
|
pub dim_foreground: Color,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Deserialize, Default)]
|
||||||
|
pub struct ColorScheme {
|
||||||
|
pub name: String,
|
||||||
|
pub is_light: bool,
|
||||||
|
|
||||||
|
pub ramps: RampSet,
|
||||||
|
|
||||||
|
pub lowest: Layer,
|
||||||
|
pub middle: Layer,
|
||||||
|
pub highest: Layer,
|
||||||
|
|
||||||
|
pub popover_shadow: Shadow,
|
||||||
|
pub modal_shadow: Shadow,
|
||||||
|
|
||||||
|
pub players: Vec<Player>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Deserialize, Default)]
|
||||||
|
pub struct Player {
|
||||||
|
pub cursor: Color,
|
||||||
|
pub selection: Color,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Deserialize, Default)]
|
||||||
|
pub struct RampSet {
|
||||||
|
pub neutral: Vec<Color>,
|
||||||
|
pub red: Vec<Color>,
|
||||||
|
pub orange: Vec<Color>,
|
||||||
|
pub yellow: Vec<Color>,
|
||||||
|
pub green: Vec<Color>,
|
||||||
|
pub cyan: Vec<Color>,
|
||||||
|
pub blue: Vec<Color>,
|
||||||
|
pub violet: Vec<Color>,
|
||||||
|
pub magenta: Vec<Color>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Deserialize, Default)]
|
||||||
|
pub struct Layer {
|
||||||
|
pub base: StyleSet,
|
||||||
|
pub variant: StyleSet,
|
||||||
|
pub on: StyleSet,
|
||||||
|
pub accent: StyleSet,
|
||||||
|
pub positive: StyleSet,
|
||||||
|
pub warning: StyleSet,
|
||||||
|
pub negative: StyleSet,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Deserialize, Default)]
|
||||||
|
pub struct StyleSet {
|
||||||
|
pub default: Style,
|
||||||
|
pub active: Style,
|
||||||
|
pub disabled: Style,
|
||||||
|
pub hovered: Style,
|
||||||
|
pub pressed: Style,
|
||||||
|
pub inverted: Style,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Deserialize, Default)]
|
||||||
|
pub struct Style {
|
||||||
|
pub background: Color,
|
||||||
|
pub border: Color,
|
||||||
|
pub foreground: Color,
|
||||||
|
}
|
||||||
|
|
18
crates/theme_testbench/Cargo.toml
Normal file
18
crates/theme_testbench/Cargo.toml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
[package]
|
||||||
|
name = "theme_testbench"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
path = "src/theme_testbench.rs"
|
||||||
|
doctest = false
|
||||||
|
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
gpui = { path = "../gpui" }
|
||||||
|
theme = { path = "../theme" }
|
||||||
|
settings = { path = "../settings" }
|
||||||
|
workspace = { path = "../workspace" }
|
||||||
|
project = { path = "../project" }
|
||||||
|
|
||||||
|
smallvec = { version = "1.6", features = ["union"] }
|
357
crates/theme_testbench/src/theme_testbench.rs
Normal file
357
crates/theme_testbench/src/theme_testbench.rs
Normal file
|
@ -0,0 +1,357 @@
|
||||||
|
use gpui::{
|
||||||
|
actions,
|
||||||
|
color::Color,
|
||||||
|
elements::{
|
||||||
|
Canvas, Container, ContainerStyle, ElementBox, Flex, Label, Margin, MouseEventHandler,
|
||||||
|
Padding, ParentElement,
|
||||||
|
},
|
||||||
|
fonts::TextStyle,
|
||||||
|
Border, Element, Entity, MutableAppContext, Quad, RenderContext, View, ViewContext,
|
||||||
|
};
|
||||||
|
use project::{Project, ProjectEntryId, ProjectPath};
|
||||||
|
use settings::Settings;
|
||||||
|
use smallvec::SmallVec;
|
||||||
|
use theme::{ColorScheme, Layer, Style, StyleSet};
|
||||||
|
use workspace::{Item, Workspace};
|
||||||
|
|
||||||
|
actions!(theme, [DeployThemeTestbench]);
|
||||||
|
|
||||||
|
pub fn init(cx: &mut MutableAppContext) {
|
||||||
|
cx.add_action(ThemeTestbench::deploy);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ThemeTestbench {}
|
||||||
|
|
||||||
|
impl ThemeTestbench {
|
||||||
|
pub fn deploy(
|
||||||
|
workspace: &mut Workspace,
|
||||||
|
_: &DeployThemeTestbench,
|
||||||
|
cx: &mut ViewContext<Workspace>,
|
||||||
|
) {
|
||||||
|
let view = cx.add_view(|_| ThemeTestbench {});
|
||||||
|
workspace.add_item(Box::new(view), cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_ramps(color_scheme: &ColorScheme) -> Flex {
|
||||||
|
fn display_ramp(ramp: &Vec<Color>) -> ElementBox {
|
||||||
|
Flex::row()
|
||||||
|
.with_children(ramp.iter().cloned().map(|color| {
|
||||||
|
Canvas::new(move |bounds, _, cx| {
|
||||||
|
cx.scene.push_quad(Quad {
|
||||||
|
bounds,
|
||||||
|
background: Some(color),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.flex(1.0, false)
|
||||||
|
.boxed()
|
||||||
|
}))
|
||||||
|
.flex(1.0, false)
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
Flex::column()
|
||||||
|
.with_child(display_ramp(&color_scheme.ramps.neutral))
|
||||||
|
.with_child(display_ramp(&color_scheme.ramps.red))
|
||||||
|
.with_child(display_ramp(&color_scheme.ramps.orange))
|
||||||
|
.with_child(display_ramp(&color_scheme.ramps.yellow))
|
||||||
|
.with_child(display_ramp(&color_scheme.ramps.green))
|
||||||
|
.with_child(display_ramp(&color_scheme.ramps.cyan))
|
||||||
|
.with_child(display_ramp(&color_scheme.ramps.blue))
|
||||||
|
.with_child(display_ramp(&color_scheme.ramps.violet))
|
||||||
|
.with_child(display_ramp(&color_scheme.ramps.magenta))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_layer(
|
||||||
|
layer_index: usize,
|
||||||
|
layer: &Layer,
|
||||||
|
cx: &mut RenderContext<'_, Self>,
|
||||||
|
) -> Container {
|
||||||
|
Flex::column()
|
||||||
|
.with_child(
|
||||||
|
Self::render_button_set(0, layer_index, "base", &layer.base, cx)
|
||||||
|
.flex(1., false)
|
||||||
|
.boxed(),
|
||||||
|
)
|
||||||
|
.with_child(
|
||||||
|
Self::render_button_set(1, layer_index, "variant", &layer.variant, cx)
|
||||||
|
.flex(1., false)
|
||||||
|
.boxed(),
|
||||||
|
)
|
||||||
|
.with_child(
|
||||||
|
Self::render_button_set(2, layer_index, "on", &layer.on, cx)
|
||||||
|
.flex(1., false)
|
||||||
|
.boxed(),
|
||||||
|
)
|
||||||
|
.with_child(
|
||||||
|
Self::render_button_set(3, layer_index, "accent", &layer.accent, cx)
|
||||||
|
.flex(1., false)
|
||||||
|
.boxed(),
|
||||||
|
)
|
||||||
|
.with_child(
|
||||||
|
Self::render_button_set(4, layer_index, "positive", &layer.positive, cx)
|
||||||
|
.flex(1., false)
|
||||||
|
.boxed(),
|
||||||
|
)
|
||||||
|
.with_child(
|
||||||
|
Self::render_button_set(5, layer_index, "warning", &layer.warning, cx)
|
||||||
|
.flex(1., false)
|
||||||
|
.boxed(),
|
||||||
|
)
|
||||||
|
.with_child(
|
||||||
|
Self::render_button_set(6, layer_index, "negative", &layer.negative, cx)
|
||||||
|
.flex(1., false)
|
||||||
|
.boxed(),
|
||||||
|
)
|
||||||
|
.contained()
|
||||||
|
.with_style(ContainerStyle {
|
||||||
|
margin: Margin {
|
||||||
|
top: 10.,
|
||||||
|
bottom: 10.,
|
||||||
|
left: 10.,
|
||||||
|
right: 10.,
|
||||||
|
},
|
||||||
|
background_color: Some(layer.base.default.background),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_button_set(
|
||||||
|
set_index: usize,
|
||||||
|
layer_index: usize,
|
||||||
|
set_name: &'static str,
|
||||||
|
style_set: &StyleSet,
|
||||||
|
cx: &mut RenderContext<'_, Self>,
|
||||||
|
) -> Flex {
|
||||||
|
Flex::row()
|
||||||
|
.with_child(Self::render_button(
|
||||||
|
set_index * 6,
|
||||||
|
layer_index,
|
||||||
|
set_name,
|
||||||
|
&style_set,
|
||||||
|
None,
|
||||||
|
cx,
|
||||||
|
))
|
||||||
|
.with_child(Self::render_button(
|
||||||
|
set_index * 6 + 1,
|
||||||
|
layer_index,
|
||||||
|
"hovered",
|
||||||
|
&style_set,
|
||||||
|
Some(|style_set| &style_set.hovered),
|
||||||
|
cx,
|
||||||
|
))
|
||||||
|
.with_child(Self::render_button(
|
||||||
|
set_index * 6 + 2,
|
||||||
|
layer_index,
|
||||||
|
"pressed",
|
||||||
|
&style_set,
|
||||||
|
Some(|style_set| &style_set.pressed),
|
||||||
|
cx,
|
||||||
|
))
|
||||||
|
.with_child(Self::render_button(
|
||||||
|
set_index * 6 + 3,
|
||||||
|
layer_index,
|
||||||
|
"active",
|
||||||
|
&style_set,
|
||||||
|
Some(|style_set| &style_set.active),
|
||||||
|
cx,
|
||||||
|
))
|
||||||
|
.with_child(Self::render_button(
|
||||||
|
set_index * 6 + 4,
|
||||||
|
layer_index,
|
||||||
|
"disabled",
|
||||||
|
&style_set,
|
||||||
|
Some(|style_set| &style_set.disabled),
|
||||||
|
cx,
|
||||||
|
))
|
||||||
|
.with_child(Self::render_button(
|
||||||
|
set_index * 6 + 5,
|
||||||
|
layer_index,
|
||||||
|
"inverted",
|
||||||
|
&style_set,
|
||||||
|
Some(|style_set| &style_set.inverted),
|
||||||
|
cx,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_button(
|
||||||
|
button_index: usize,
|
||||||
|
layer_index: usize,
|
||||||
|
text: &'static str,
|
||||||
|
style_set: &StyleSet,
|
||||||
|
style_override: Option<fn(&StyleSet) -> &Style>,
|
||||||
|
cx: &mut RenderContext<'_, Self>,
|
||||||
|
) -> ElementBox {
|
||||||
|
enum TestBenchButton {}
|
||||||
|
MouseEventHandler::<TestBenchButton>::new(layer_index + button_index, cx, |state, cx| {
|
||||||
|
let style = if let Some(style_override) = style_override {
|
||||||
|
style_override(&style_set)
|
||||||
|
} else if state.clicked().is_some() {
|
||||||
|
&style_set.pressed
|
||||||
|
} else if state.hovered() {
|
||||||
|
&style_set.hovered
|
||||||
|
} else {
|
||||||
|
&style_set.default
|
||||||
|
};
|
||||||
|
|
||||||
|
Self::render_label(text.to_string(), style, cx)
|
||||||
|
.contained()
|
||||||
|
.with_style(ContainerStyle {
|
||||||
|
margin: Margin {
|
||||||
|
top: 4.,
|
||||||
|
bottom: 4.,
|
||||||
|
left: 4.,
|
||||||
|
right: 4.,
|
||||||
|
},
|
||||||
|
padding: Padding {
|
||||||
|
top: 4.,
|
||||||
|
bottom: 4.,
|
||||||
|
left: 4.,
|
||||||
|
right: 4.,
|
||||||
|
},
|
||||||
|
background_color: Some(style.background),
|
||||||
|
border: Border {
|
||||||
|
width: 1.,
|
||||||
|
color: style.border,
|
||||||
|
overlay: false,
|
||||||
|
top: true,
|
||||||
|
bottom: true,
|
||||||
|
left: true,
|
||||||
|
right: true,
|
||||||
|
},
|
||||||
|
corner_radius: 2.,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.boxed()
|
||||||
|
})
|
||||||
|
.flex(1., true)
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_label(text: String, style: &Style, cx: &mut RenderContext<'_, Self>) -> Label {
|
||||||
|
let settings = cx.global::<Settings>();
|
||||||
|
let font_cache = cx.font_cache();
|
||||||
|
let family_id = settings.buffer_font_family;
|
||||||
|
let font_size = settings.buffer_font_size;
|
||||||
|
let font_id = font_cache
|
||||||
|
.select_font(family_id, &Default::default())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let text_style = TextStyle {
|
||||||
|
color: style.foreground,
|
||||||
|
font_family_id: family_id,
|
||||||
|
font_family_name: font_cache.family_name(family_id).unwrap(),
|
||||||
|
font_id,
|
||||||
|
font_size,
|
||||||
|
font_properties: Default::default(),
|
||||||
|
underline: Default::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Label::new(text, text_style)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Entity for ThemeTestbench {
|
||||||
|
type Event = ();
|
||||||
|
}
|
||||||
|
|
||||||
|
impl View for ThemeTestbench {
|
||||||
|
fn ui_name() -> &'static str {
|
||||||
|
"ThemeTestbench"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox {
|
||||||
|
let color_scheme = &cx.global::<Settings>().theme.clone().color_scheme;
|
||||||
|
|
||||||
|
Flex::row()
|
||||||
|
.with_child(
|
||||||
|
Self::render_ramps(color_scheme)
|
||||||
|
.contained()
|
||||||
|
.with_margin_right(10.)
|
||||||
|
.flex(0.1, false)
|
||||||
|
.boxed(),
|
||||||
|
)
|
||||||
|
.with_child(
|
||||||
|
Flex::column()
|
||||||
|
.with_child(
|
||||||
|
Self::render_layer(100, &color_scheme.lowest, cx)
|
||||||
|
.flex(1., true)
|
||||||
|
.boxed(),
|
||||||
|
)
|
||||||
|
.with_child(
|
||||||
|
Self::render_layer(200, &color_scheme.middle, cx)
|
||||||
|
.flex(1., true)
|
||||||
|
.boxed(),
|
||||||
|
)
|
||||||
|
.with_child(
|
||||||
|
Self::render_layer(300, &color_scheme.highest, cx)
|
||||||
|
.flex(1., true)
|
||||||
|
.boxed(),
|
||||||
|
)
|
||||||
|
.flex(1., false)
|
||||||
|
.boxed(),
|
||||||
|
)
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Item for ThemeTestbench {
|
||||||
|
fn tab_content(
|
||||||
|
&self,
|
||||||
|
_: Option<usize>,
|
||||||
|
style: &theme::Tab,
|
||||||
|
_: &gpui::AppContext,
|
||||||
|
) -> gpui::ElementBox {
|
||||||
|
Label::new("Theme Testbench".into(), style.label.clone())
|
||||||
|
.aligned()
|
||||||
|
.contained()
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn project_path(&self, _: &gpui::AppContext) -> Option<ProjectPath> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn project_entry_ids(&self, _: &gpui::AppContext) -> SmallVec<[ProjectEntryId; 3]> {
|
||||||
|
SmallVec::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_singleton(&self, _: &gpui::AppContext) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_nav_history(&mut self, _: workspace::ItemNavHistory, _: &mut ViewContext<Self>) {}
|
||||||
|
|
||||||
|
fn can_save(&self, _: &gpui::AppContext) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save(
|
||||||
|
&mut self,
|
||||||
|
_: gpui::ModelHandle<Project>,
|
||||||
|
_: &mut ViewContext<Self>,
|
||||||
|
) -> gpui::Task<gpui::anyhow::Result<()>> {
|
||||||
|
unreachable!("save should not have been called");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save_as(
|
||||||
|
&mut self,
|
||||||
|
_: gpui::ModelHandle<Project>,
|
||||||
|
_: std::path::PathBuf,
|
||||||
|
_: &mut ViewContext<Self>,
|
||||||
|
) -> gpui::Task<gpui::anyhow::Result<()>> {
|
||||||
|
unreachable!("save_as should not have been called");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reload(
|
||||||
|
&mut self,
|
||||||
|
_: gpui::ModelHandle<Project>,
|
||||||
|
_: &mut ViewContext<Self>,
|
||||||
|
) -> gpui::Task<gpui::anyhow::Result<()>> {
|
||||||
|
gpui::Task::ready(Ok(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_item_events(_: &Self::Event) -> Vec<workspace::ItemEvent> {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,7 +20,6 @@ assets = { path = "../assets" }
|
||||||
auto_update = { path = "../auto_update" }
|
auto_update = { path = "../auto_update" }
|
||||||
breadcrumbs = { path = "../breadcrumbs" }
|
breadcrumbs = { path = "../breadcrumbs" }
|
||||||
call = { path = "../call" }
|
call = { path = "../call" }
|
||||||
chat_panel = { path = "../chat_panel" }
|
|
||||||
cli = { path = "../cli" }
|
cli = { path = "../cli" }
|
||||||
collab_ui = { path = "../collab_ui" }
|
collab_ui = { path = "../collab_ui" }
|
||||||
collections = { path = "../collections" }
|
collections = { path = "../collections" }
|
||||||
|
@ -52,6 +51,7 @@ text = { path = "../text" }
|
||||||
terminal = { path = "../terminal" }
|
terminal = { path = "../terminal" }
|
||||||
theme = { path = "../theme" }
|
theme = { path = "../theme" }
|
||||||
theme_selector = { path = "../theme_selector" }
|
theme_selector = { path = "../theme_selector" }
|
||||||
|
theme_testbench = { path = "../theme_testbench" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
vim = { path = "../vim" }
|
vim = { path = "../vim" }
|
||||||
workspace = { path = "../workspace" }
|
workspace = { path = "../workspace" }
|
||||||
|
|
|
@ -21,12 +21,12 @@ fn main() {
|
||||||
|
|
||||||
let output = Command::new("npm")
|
let output = Command::new("npm")
|
||||||
.current_dir("../../styles")
|
.current_dir("../../styles")
|
||||||
.args(["run", "build-themes"])
|
.args(["run", "build"])
|
||||||
.output()
|
.output()
|
||||||
.expect("failed to run npm");
|
.expect("failed to run npm");
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
panic!(
|
panic!(
|
||||||
"build-themes script failed {}",
|
"build script failed {}",
|
||||||
String::from_utf8_lossy(&output.stderr)
|
String::from_utf8_lossy(&output.stderr)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,7 +116,6 @@ fn main() {
|
||||||
editor::init(cx);
|
editor::init(cx);
|
||||||
go_to_line::init(cx);
|
go_to_line::init(cx);
|
||||||
file_finder::init(cx);
|
file_finder::init(cx);
|
||||||
chat_panel::init(cx);
|
|
||||||
outline::init(cx);
|
outline::init(cx);
|
||||||
project_symbols::init(cx);
|
project_symbols::init(cx);
|
||||||
project_panel::init(cx);
|
project_panel::init(cx);
|
||||||
|
@ -124,6 +123,7 @@ fn main() {
|
||||||
search::init(cx);
|
search::init(cx);
|
||||||
vim::init(cx);
|
vim::init(cx);
|
||||||
terminal::init(cx);
|
terminal::init(cx);
|
||||||
|
theme_testbench::init(cx);
|
||||||
|
|
||||||
cx.spawn(|cx| watch_themes(fs.clone(), themes.clone(), cx))
|
cx.spawn(|cx| watch_themes(fs.clone(), themes.clone(), cx))
|
||||||
.detach();
|
.detach();
|
||||||
|
@ -441,7 +441,7 @@ async fn watch_themes(
|
||||||
while (events.next().await).is_some() {
|
while (events.next().await).is_some() {
|
||||||
let output = Command::new("npm")
|
let output = Command::new("npm")
|
||||||
.current_dir("styles")
|
.current_dir("styles")
|
||||||
.args(["run", "build-themes"])
|
.args(["run", "build"])
|
||||||
.output()
|
.output()
|
||||||
.await
|
.await
|
||||||
.log_err()?;
|
.log_err()?;
|
||||||
|
@ -449,7 +449,7 @@ async fn watch_themes(
|
||||||
cx.update(|cx| theme_selector::ThemeSelector::reload(themes.clone(), cx))
|
cx.update(|cx| theme_selector::ThemeSelector::reload(themes.clone(), cx))
|
||||||
} else {
|
} else {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"build-themes script failed {}",
|
"build script failed {}",
|
||||||
String::from_utf8_lossy(&output.stderr)
|
String::from_utf8_lossy(&output.stderr)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
ZED_SERVER_URL=http://localhost:3000 cargo run $@
|
ZED_ADMIN_API_TOKEN=secret ZED_SERVER_URL=http://localhost:3000 cargo run $@
|
||||||
|
|
|
@ -1,19 +1,18 @@
|
||||||
{
|
{
|
||||||
"name": "styles",
|
"name": "styles",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "npm run build-themes && npm run build-tokens",
|
"build": "ts-node ./src/buildThemes.ts"
|
||||||
"build-themes": "ts-node ./src/buildThemes.ts"
|
},
|
||||||
},
|
"author": "",
|
||||||
"author": "",
|
"license": "ISC",
|
||||||
"license": "ISC",
|
"dependencies": {
|
||||||
"dependencies": {
|
"@types/chroma-js": "^2.1.3",
|
||||||
"@types/chroma-js": "^2.1.3",
|
"@types/node": "^17.0.23",
|
||||||
"@types/node": "^17.0.23",
|
"case-anything": "^2.1.10",
|
||||||
"case-anything": "^2.1.10",
|
"chroma-js": "^2.4.2",
|
||||||
"chroma-js": "^2.4.2",
|
"ts-node": "^10.7.0"
|
||||||
"ts-node": "^10.7.0"
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,12 @@ import * as fs from "fs";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import { tmpdir } from "os";
|
import { tmpdir } from "os";
|
||||||
import app from "./styleTree/app";
|
import app from "./styleTree/app";
|
||||||
import themes, { internalThemes, experimentalThemes } from "./themes";
|
import colorSchemes, {
|
||||||
|
internalColorSchemes,
|
||||||
|
experimentalColorSchemes,
|
||||||
|
} from "./colorSchemes";
|
||||||
import snakeCase from "./utils/snakeCase";
|
import snakeCase from "./utils/snakeCase";
|
||||||
import Theme from "./themes/common/theme";
|
import { ColorScheme } from "./themes/common/colorScheme";
|
||||||
|
|
||||||
const themeDirectory = `${__dirname}/../../assets/themes`;
|
const themeDirectory = `${__dirname}/../../assets/themes`;
|
||||||
const internalDirectory = `${themeDirectory}/internal`;
|
const internalDirectory = `${themeDirectory}/internal`;
|
||||||
|
@ -16,7 +19,7 @@ function clearThemes(themeDirectory: string) {
|
||||||
for (const file of fs.readdirSync(themeDirectory)) {
|
for (const file of fs.readdirSync(themeDirectory)) {
|
||||||
if (file.endsWith(".json")) {
|
if (file.endsWith(".json")) {
|
||||||
const name = file.replace(/\.json$/, "");
|
const name = file.replace(/\.json$/, "");
|
||||||
if (!themes.find((theme) => theme.name === name)) {
|
if (!colorSchemes.find((colorScheme) => colorScheme.name === name)) {
|
||||||
fs.unlinkSync(path.join(themeDirectory, file));
|
fs.unlinkSync(path.join(themeDirectory, file));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,12 +30,12 @@ clearThemes(themeDirectory);
|
||||||
clearThemes(internalDirectory);
|
clearThemes(internalDirectory);
|
||||||
clearThemes(experimentsDirectory);
|
clearThemes(experimentsDirectory);
|
||||||
|
|
||||||
function writeThemes(themes: Theme[], outputDirectory: string) {
|
function writeThemes(colorSchemes: ColorScheme[], outputDirectory: string) {
|
||||||
for (let theme of themes) {
|
for (let colorScheme of colorSchemes) {
|
||||||
let styleTree = snakeCase(app(theme));
|
let styleTree = snakeCase(app(colorScheme));
|
||||||
let styleTreeJSON = JSON.stringify(styleTree, null, 2);
|
let styleTreeJSON = JSON.stringify(styleTree, null, 2);
|
||||||
let tempPath = path.join(tempDirectory, `${theme.name}.json`);
|
let tempPath = path.join(tempDirectory, `${colorScheme.name}.json`);
|
||||||
let outPath = path.join(outputDirectory, `${theme.name}.json`);
|
let outPath = path.join(outputDirectory, `${colorScheme.name}.json`);
|
||||||
fs.writeFileSync(tempPath, styleTreeJSON);
|
fs.writeFileSync(tempPath, styleTreeJSON);
|
||||||
fs.renameSync(tempPath, outPath);
|
fs.renameSync(tempPath, outPath);
|
||||||
console.log(`- ${outPath} created`);
|
console.log(`- ${outPath} created`);
|
||||||
|
@ -40,6 +43,6 @@ function writeThemes(themes: Theme[], outputDirectory: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write new themes to theme directory
|
// Write new themes to theme directory
|
||||||
writeThemes(themes, themeDirectory);
|
writeThemes(colorSchemes, themeDirectory);
|
||||||
writeThemes(internalThemes, internalDirectory);
|
writeThemes(internalColorSchemes, internalDirectory);
|
||||||
writeThemes(experimentalThemes, experimentsDirectory);
|
writeThemes(experimentalColorSchemes, experimentsDirectory);
|
||||||
|
|
35
styles/src/colorSchemes.ts
Normal file
35
styles/src/colorSchemes.ts
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import fs from "fs";
|
||||||
|
import path from "path";
|
||||||
|
import { ColorScheme } from "./themes/common/colorScheme";
|
||||||
|
|
||||||
|
const colorSchemes: ColorScheme[] = [];
|
||||||
|
export default colorSchemes;
|
||||||
|
|
||||||
|
const internalColorSchemes: ColorScheme[] = [];
|
||||||
|
export { internalColorSchemes };
|
||||||
|
|
||||||
|
const experimentalColorSchemes: ColorScheme[] = [];
|
||||||
|
export { experimentalColorSchemes };
|
||||||
|
|
||||||
|
function fillColorSchemes(themesPath: string, colorSchemes: ColorScheme[]) {
|
||||||
|
for (const fileName of fs.readdirSync(themesPath)) {
|
||||||
|
if (fileName == "template.ts") continue;
|
||||||
|
const filePath = path.join(themesPath, fileName);
|
||||||
|
|
||||||
|
if (fs.statSync(filePath).isFile()) {
|
||||||
|
const colorScheme = require(filePath);
|
||||||
|
if (colorScheme.dark) colorSchemes.push(colorScheme.dark);
|
||||||
|
if (colorScheme.light) colorSchemes.push(colorScheme.light);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fillColorSchemes(path.resolve(`${__dirname}/themes`), colorSchemes);
|
||||||
|
fillColorSchemes(
|
||||||
|
path.resolve(`${__dirname}/themes/internal`),
|
||||||
|
internalColorSchemes
|
||||||
|
);
|
||||||
|
fillColorSchemes(
|
||||||
|
path.resolve(`${__dirname}/themes/experiments`),
|
||||||
|
experimentalColorSchemes
|
||||||
|
);
|
|
@ -1,5 +1,3 @@
|
||||||
import Theme from "../themes/common/theme";
|
|
||||||
import chatPanel from "./chatPanel";
|
|
||||||
import { text } from "./components";
|
import { text } from "./components";
|
||||||
import contactFinder from "./contactFinder";
|
import contactFinder from "./contactFinder";
|
||||||
import contactsPopover from "./contactsPopover";
|
import contactsPopover from "./contactsPopover";
|
||||||
|
@ -18,40 +16,51 @@ import tooltip from "./tooltip";
|
||||||
import terminal from "./terminal";
|
import terminal from "./terminal";
|
||||||
import contactList from "./contactList";
|
import contactList from "./contactList";
|
||||||
import incomingCallNotification from "./incomingCallNotification";
|
import incomingCallNotification from "./incomingCallNotification";
|
||||||
|
import { ColorScheme } from "../themes/common/colorScheme";
|
||||||
|
|
||||||
export const panel = {
|
export default function app(colorScheme: ColorScheme): Object {
|
||||||
padding: { top: 12, bottom: 12 },
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function app(theme: Theme): Object {
|
|
||||||
return {
|
return {
|
||||||
meta: {
|
meta: {
|
||||||
name: theme.name,
|
name: colorScheme.name,
|
||||||
isLight: theme.isLight
|
isLight: colorScheme.isLight,
|
||||||
},
|
},
|
||||||
picker: picker(theme),
|
commandPalette: commandPalette(colorScheme),
|
||||||
workspace: workspace(theme),
|
contactNotification: contactNotification(colorScheme),
|
||||||
contextMenu: contextMenu(theme),
|
projectSharedNotification: projectSharedNotification(colorScheme),
|
||||||
editor: editor(theme),
|
incomingCallNotification: incomingCallNotification(colorScheme),
|
||||||
projectDiagnostics: projectDiagnostics(theme),
|
picker: picker(colorScheme),
|
||||||
commandPalette: commandPalette(theme),
|
workspace: workspace(colorScheme),
|
||||||
projectPanel: projectPanel(theme),
|
contextMenu: contextMenu(colorScheme),
|
||||||
chatPanel: chatPanel(theme),
|
editor: editor(colorScheme),
|
||||||
contactsPopover: contactsPopover(theme),
|
projectDiagnostics: projectDiagnostics(colorScheme),
|
||||||
contactList: contactList(theme),
|
projectPanel: projectPanel(colorScheme),
|
||||||
contactFinder: contactFinder(theme),
|
contactsPopover: contactsPopover(colorScheme),
|
||||||
search: search(theme),
|
contactFinder: contactFinder(colorScheme),
|
||||||
|
contactList: contactList(colorScheme),
|
||||||
|
search: search(colorScheme),
|
||||||
breadcrumbs: {
|
breadcrumbs: {
|
||||||
...text(theme, "sans", "secondary"),
|
...text(colorScheme.highest, "sans", "variant"),
|
||||||
padding: {
|
padding: {
|
||||||
left: 6,
|
left: 6,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
contactNotification: contactNotification(theme),
|
updateNotification: updateNotification(colorScheme),
|
||||||
updateNotification: updateNotification(theme),
|
tooltip: tooltip(colorScheme),
|
||||||
projectSharedNotification: projectSharedNotification(theme),
|
terminal: terminal(colorScheme),
|
||||||
incomingCallNotification: incomingCallNotification(theme),
|
colorScheme: {
|
||||||
tooltip: tooltip(theme),
|
...colorScheme,
|
||||||
terminal: terminal(theme),
|
players: Object.values(colorScheme.players),
|
||||||
|
ramps: {
|
||||||
|
neutral: colorScheme.ramps.neutral.colors(100, "hex"),
|
||||||
|
red: colorScheme.ramps.red.colors(100, "hex"),
|
||||||
|
orange: colorScheme.ramps.orange.colors(100, "hex"),
|
||||||
|
yellow: colorScheme.ramps.yellow.colors(100, "hex"),
|
||||||
|
green: colorScheme.ramps.green.colors(100, "hex"),
|
||||||
|
cyan: colorScheme.ramps.cyan.colors(100, "hex"),
|
||||||
|
blue: colorScheme.ramps.blue.colors(100, "hex"),
|
||||||
|
violet: colorScheme.ramps.violet.colors(100, "hex"),
|
||||||
|
magenta: colorScheme.ramps.magenta.colors(100, "hex"),
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,108 +0,0 @@
|
||||||
import Theme from "../themes/common/theme";
|
|
||||||
import { panel } from "./app";
|
|
||||||
import {
|
|
||||||
backgroundColor,
|
|
||||||
border,
|
|
||||||
player,
|
|
||||||
text,
|
|
||||||
TextColor,
|
|
||||||
popoverShadow,
|
|
||||||
} from "./components";
|
|
||||||
|
|
||||||
export default function chatPanel(theme: Theme) {
|
|
||||||
function channelSelectItem(
|
|
||||||
theme: Theme,
|
|
||||||
textColor: TextColor,
|
|
||||||
hovered: boolean
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
name: text(theme, "sans", textColor),
|
|
||||||
padding: 4,
|
|
||||||
hash: {
|
|
||||||
...text(theme, "sans", "muted"),
|
|
||||||
margin: {
|
|
||||||
right: 8,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
background: hovered ? backgroundColor(theme, 300, "hovered") : undefined,
|
|
||||||
cornerRadius: hovered ? 6 : 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const message = {
|
|
||||||
body: text(theme, "sans", "secondary"),
|
|
||||||
timestamp: text(theme, "sans", "muted", { size: "sm" }),
|
|
||||||
padding: {
|
|
||||||
bottom: 6,
|
|
||||||
},
|
|
||||||
sender: {
|
|
||||||
...text(theme, "sans", "primary", { weight: "bold" }),
|
|
||||||
margin: {
|
|
||||||
right: 8,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
...panel,
|
|
||||||
channelName: text(theme, "sans", "primary", { weight: "bold" }),
|
|
||||||
channelNameHash: {
|
|
||||||
...text(theme, "sans", "muted"),
|
|
||||||
padding: {
|
|
||||||
right: 8,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
channelSelect: {
|
|
||||||
header: {
|
|
||||||
...channelSelectItem(theme, "primary", false),
|
|
||||||
padding: {
|
|
||||||
bottom: 4,
|
|
||||||
left: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
item: channelSelectItem(theme, "secondary", false),
|
|
||||||
hoveredItem: channelSelectItem(theme, "secondary", true),
|
|
||||||
activeItem: channelSelectItem(theme, "primary", false),
|
|
||||||
hoveredActiveItem: channelSelectItem(theme, "primary", true),
|
|
||||||
menu: {
|
|
||||||
background: backgroundColor(theme, 500),
|
|
||||||
cornerRadius: 6,
|
|
||||||
padding: 4,
|
|
||||||
border: border(theme, "primary"),
|
|
||||||
shadow: popoverShadow(theme),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
signInPrompt: text(theme, "sans", "secondary", { underline: true }),
|
|
||||||
hoveredSignInPrompt: text(theme, "sans", "primary", { underline: true }),
|
|
||||||
message,
|
|
||||||
pendingMessage: {
|
|
||||||
...message,
|
|
||||||
body: {
|
|
||||||
...message.body,
|
|
||||||
color: theme.textColor.muted,
|
|
||||||
},
|
|
||||||
sender: {
|
|
||||||
...message.sender,
|
|
||||||
color: theme.textColor.muted,
|
|
||||||
},
|
|
||||||
timestamp: {
|
|
||||||
...message.timestamp,
|
|
||||||
color: theme.textColor.muted,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
inputEditor: {
|
|
||||||
background: backgroundColor(theme, 500),
|
|
||||||
cornerRadius: 6,
|
|
||||||
text: text(theme, "mono", "primary"),
|
|
||||||
placeholderText: text(theme, "mono", "placeholder", { size: "sm" }),
|
|
||||||
selection: player(theme, 1).selection,
|
|
||||||
border: border(theme, "secondary"),
|
|
||||||
padding: {
|
|
||||||
bottom: 7,
|
|
||||||
left: 8,
|
|
||||||
right: 8,
|
|
||||||
top: 7,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,25 +1,29 @@
|
||||||
import Theme from "../themes/common/theme";
|
import { ColorScheme } from "../themes/common/colorScheme";
|
||||||
import { text, backgroundColor, border } from "./components";
|
import { withOpacity } from "../utils/color";
|
||||||
|
import { text, background } from "./components";
|
||||||
|
|
||||||
export default function commandPalette(theme: Theme) {
|
export default function commandPalette(colorScheme: ColorScheme) {
|
||||||
|
let layer = colorScheme.highest;
|
||||||
return {
|
return {
|
||||||
keystrokeSpacing: 8,
|
keystrokeSpacing: 8,
|
||||||
key: {
|
key: {
|
||||||
text: text(theme, "mono", "secondary", { size: "xs" }),
|
text: text(layer, "mono", "variant", "default", { size: "xs" }),
|
||||||
cornerRadius: 4,
|
cornerRadius: 2,
|
||||||
background: backgroundColor(theme, "on300"),
|
background: background(layer, "on"),
|
||||||
border: border(theme, "secondary"),
|
|
||||||
padding: {
|
padding: {
|
||||||
top: 2,
|
top: 1,
|
||||||
bottom: 2,
|
bottom: 1,
|
||||||
left: 8,
|
left: 6,
|
||||||
right: 8,
|
right: 6,
|
||||||
},
|
},
|
||||||
margin: {
|
margin: {
|
||||||
|
top: 1,
|
||||||
|
bottom: 1,
|
||||||
left: 2,
|
left: 2,
|
||||||
},
|
},
|
||||||
active: {
|
active: {
|
||||||
text: text(theme, "mono", "active", { size: "xs" }),
|
text: text(layer, "mono", "on", "default", { size: "xs" }),
|
||||||
|
background: withOpacity(background(layer, "on"), 0.2),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,31 +1,157 @@
|
||||||
import Theme, { BackgroundColorSet } from "../themes/common/theme";
|
|
||||||
import { fontFamilies, fontSizes, FontWeight } from "../common";
|
import { fontFamilies, fontSizes, FontWeight } from "../common";
|
||||||
|
import { Layer, Styles, StyleSets, Style } from "../themes/common/colorScheme";
|
||||||
|
|
||||||
export type TextColor = keyof Theme["textColor"];
|
function isStyleSet(key: any): key is StyleSets {
|
||||||
export function text(
|
return [
|
||||||
theme: Theme,
|
"base",
|
||||||
fontFamily: keyof typeof fontFamilies,
|
"variant",
|
||||||
color: TextColor,
|
"on",
|
||||||
properties?: {
|
"accent",
|
||||||
size?: keyof typeof fontSizes;
|
"positive",
|
||||||
weight?: FontWeight;
|
"warning",
|
||||||
underline?: boolean;
|
"negative",
|
||||||
|
].includes(key);
|
||||||
|
}
|
||||||
|
function isStyle(key: any): key is Styles {
|
||||||
|
return ["default", "active", "disabled", "hovered", "pressed", "inverted"].includes(key);
|
||||||
|
}
|
||||||
|
function getStyle(
|
||||||
|
layer: Layer,
|
||||||
|
possibleStyleSetOrStyle?: any,
|
||||||
|
possibleStyle?: any
|
||||||
|
): Style {
|
||||||
|
let styleSet: StyleSets = "base";
|
||||||
|
let style: Styles = "default";
|
||||||
|
if (isStyleSet(possibleStyleSetOrStyle)) {
|
||||||
|
styleSet = possibleStyleSetOrStyle;
|
||||||
|
} else if (isStyle(possibleStyleSetOrStyle)) {
|
||||||
|
style = possibleStyleSetOrStyle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isStyle(possibleStyle)) {
|
||||||
|
style = possibleStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
return layer[styleSet][style];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function background(layer: Layer, style?: Styles): string;
|
||||||
|
export function background(
|
||||||
|
layer: Layer,
|
||||||
|
styleSet?: StyleSets,
|
||||||
|
style?: Styles
|
||||||
|
): string;
|
||||||
|
export function background(
|
||||||
|
layer: Layer,
|
||||||
|
styleSetOrStyles?: StyleSets | Styles,
|
||||||
|
style?: Styles
|
||||||
|
): string {
|
||||||
|
return getStyle(layer, styleSetOrStyles, style).background;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function borderColor(layer: Layer, style?: Styles): string;
|
||||||
|
export function borderColor(
|
||||||
|
layer: Layer,
|
||||||
|
styleSet?: StyleSets,
|
||||||
|
style?: Styles
|
||||||
|
): string;
|
||||||
|
export function borderColor(
|
||||||
|
layer: Layer,
|
||||||
|
styleSetOrStyles?: StyleSets | Styles,
|
||||||
|
style?: Styles
|
||||||
|
): string {
|
||||||
|
return getStyle(layer, styleSetOrStyles, style).border;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function foreground(layer: Layer, style?: Styles): string;
|
||||||
|
export function foreground(
|
||||||
|
layer: Layer,
|
||||||
|
styleSet?: StyleSets,
|
||||||
|
style?: Styles
|
||||||
|
): string;
|
||||||
|
export function foreground(
|
||||||
|
layer: Layer,
|
||||||
|
styleSetOrStyles?: StyleSets | Styles,
|
||||||
|
style?: Styles
|
||||||
|
): string {
|
||||||
|
return getStyle(layer, styleSetOrStyles, style).foreground;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Text {
|
||||||
|
family: keyof typeof fontFamilies;
|
||||||
|
color: string;
|
||||||
|
size: number;
|
||||||
|
weight?: FontWeight;
|
||||||
|
underline?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TextProperties {
|
||||||
|
size?: keyof typeof fontSizes;
|
||||||
|
weight?: FontWeight;
|
||||||
|
underline?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function text(
|
||||||
|
layer: Layer,
|
||||||
|
fontFamily: keyof typeof fontFamilies,
|
||||||
|
styleSet: StyleSets,
|
||||||
|
style: Styles,
|
||||||
|
properties?: TextProperties
|
||||||
|
): Text;
|
||||||
|
export function text(
|
||||||
|
layer: Layer,
|
||||||
|
fontFamily: keyof typeof fontFamilies,
|
||||||
|
styleSet: StyleSets,
|
||||||
|
properties?: TextProperties
|
||||||
|
): Text;
|
||||||
|
export function text(
|
||||||
|
layer: Layer,
|
||||||
|
fontFamily: keyof typeof fontFamilies,
|
||||||
|
style: Styles,
|
||||||
|
properties?: TextProperties
|
||||||
|
): Text;
|
||||||
|
export function text(
|
||||||
|
layer: Layer,
|
||||||
|
fontFamily: keyof typeof fontFamilies,
|
||||||
|
properties?: TextProperties
|
||||||
|
): Text;
|
||||||
|
export function text(
|
||||||
|
layer: Layer,
|
||||||
|
fontFamily: keyof typeof fontFamilies,
|
||||||
|
styleSetStyleOrProperties?: StyleSets | Styles | TextProperties,
|
||||||
|
styleOrProperties?: Styles | TextProperties,
|
||||||
|
properties?: TextProperties
|
||||||
) {
|
) {
|
||||||
|
let style = getStyle(layer, styleSetStyleOrProperties, styleOrProperties);
|
||||||
|
|
||||||
|
if (typeof styleSetStyleOrProperties === "object") {
|
||||||
|
properties = styleSetStyleOrProperties;
|
||||||
|
}
|
||||||
|
if (typeof styleOrProperties === "object") {
|
||||||
|
properties = styleOrProperties;
|
||||||
|
}
|
||||||
|
|
||||||
let size = fontSizes[properties?.size || "sm"];
|
let size = fontSizes[properties?.size || "sm"];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
family: fontFamilies[fontFamily],
|
family: fontFamilies[fontFamily],
|
||||||
color: theme.textColor[color],
|
color: style.foreground,
|
||||||
...properties,
|
...properties,
|
||||||
size,
|
size,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
export function textColor(theme: Theme, color: TextColor) {
|
|
||||||
return theme.textColor[color];
|
export interface Border {
|
||||||
|
color: string;
|
||||||
|
width: number;
|
||||||
|
top?: boolean;
|
||||||
|
bottom?: boolean;
|
||||||
|
left?: boolean;
|
||||||
|
right?: boolean;
|
||||||
|
overlay?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type BorderColor = keyof Theme["borderColor"];
|
export interface BorderProperties {
|
||||||
export interface BorderOptions {
|
|
||||||
width?: number;
|
width?: number;
|
||||||
top?: boolean;
|
top?: boolean;
|
||||||
bottom?: boolean;
|
bottom?: boolean;
|
||||||
|
@ -33,72 +159,42 @@ export interface BorderOptions {
|
||||||
right?: boolean;
|
right?: boolean;
|
||||||
overlay?: boolean;
|
overlay?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function border(
|
export function border(
|
||||||
theme: Theme,
|
layer: Layer,
|
||||||
color: BorderColor,
|
styleSet: StyleSets,
|
||||||
options?: BorderOptions
|
style: Styles,
|
||||||
) {
|
properties?: BorderProperties
|
||||||
|
): Border;
|
||||||
|
export function border(
|
||||||
|
layer: Layer,
|
||||||
|
styleSet: StyleSets,
|
||||||
|
properties?: BorderProperties
|
||||||
|
): Border;
|
||||||
|
export function border(
|
||||||
|
layer: Layer,
|
||||||
|
style: Styles,
|
||||||
|
properties?: BorderProperties
|
||||||
|
): Border;
|
||||||
|
export function border(layer: Layer, properties?: BorderProperties): Border;
|
||||||
|
export function border(
|
||||||
|
layer: Layer,
|
||||||
|
styleSetStyleOrProperties?: StyleSets | Styles | BorderProperties,
|
||||||
|
styleOrProperties?: Styles | BorderProperties,
|
||||||
|
properties?: BorderProperties
|
||||||
|
): Border {
|
||||||
|
let style = getStyle(layer, styleSetStyleOrProperties, styleOrProperties);
|
||||||
|
|
||||||
|
if (typeof styleSetStyleOrProperties === "object") {
|
||||||
|
properties = styleSetStyleOrProperties;
|
||||||
|
}
|
||||||
|
if (typeof styleOrProperties === "object") {
|
||||||
|
properties = styleOrProperties;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
color: borderColor(theme, color),
|
color: style.border,
|
||||||
width: 1,
|
width: 1,
|
||||||
...options,
|
...properties,
|
||||||
};
|
|
||||||
}
|
|
||||||
export function borderColor(theme: Theme, color: BorderColor) {
|
|
||||||
return theme.borderColor[color];
|
|
||||||
}
|
|
||||||
|
|
||||||
export type IconColor = keyof Theme["iconColor"];
|
|
||||||
export function iconColor(theme: Theme, color: IconColor) {
|
|
||||||
return theme.iconColor[color];
|
|
||||||
}
|
|
||||||
|
|
||||||
export type PlayerIndex = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
|
|
||||||
export interface Player {
|
|
||||||
selection: {
|
|
||||||
cursor: string;
|
|
||||||
selection: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
export function player(theme: Theme, playerNumber: PlayerIndex): Player {
|
|
||||||
return {
|
|
||||||
selection: {
|
|
||||||
cursor: theme.player[playerNumber].cursorColor,
|
|
||||||
selection: theme.player[playerNumber].selectionColor,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export type BackgroundColor = keyof Theme["backgroundColor"];
|
|
||||||
export type BackgroundState = keyof BackgroundColorSet;
|
|
||||||
export function backgroundColor(
|
|
||||||
theme: Theme,
|
|
||||||
name: BackgroundColor,
|
|
||||||
state?: BackgroundState
|
|
||||||
): string {
|
|
||||||
return theme.backgroundColor[name][state || "base"];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function modalShadow(theme: Theme) {
|
|
||||||
return {
|
|
||||||
blur: 16,
|
|
||||||
color: theme.shadow,
|
|
||||||
offset: [0, 2],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function popoverShadow(theme: Theme) {
|
|
||||||
return {
|
|
||||||
blur: 4,
|
|
||||||
color: theme.shadow,
|
|
||||||
offset: [1, 2],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function draggedShadow(theme: Theme) {
|
|
||||||
return {
|
|
||||||
blur: 6,
|
|
||||||
color: theme.shadow,
|
|
||||||
offset: [1, 2],
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
import Theme from "../themes/common/theme";
|
|
||||||
import picker from "./picker";
|
import picker from "./picker";
|
||||||
import { backgroundColor, border, iconColor, player, text } from "./components";
|
import { ColorScheme } from "../themes/common/colorScheme";
|
||||||
|
import { background, border, foreground, text } from "./components";
|
||||||
|
|
||||||
|
export default function contactFinder(colorScheme: ColorScheme) {
|
||||||
|
let layer = colorScheme.highest;
|
||||||
|
|
||||||
export default function contactFinder(theme: Theme) {
|
|
||||||
const sideMargin = 6;
|
const sideMargin = 6;
|
||||||
const contactButton = {
|
const contactButton = {
|
||||||
background: backgroundColor(theme, 100),
|
background: background(layer, "variant"),
|
||||||
color: iconColor(theme, "primary"),
|
color: foreground(layer, "variant"),
|
||||||
iconWidth: 8,
|
iconWidth: 8,
|
||||||
buttonWidth: 16,
|
buttonWidth: 16,
|
||||||
cornerRadius: 8,
|
cornerRadius: 8,
|
||||||
|
@ -15,17 +17,17 @@ export default function contactFinder(theme: Theme) {
|
||||||
return {
|
return {
|
||||||
picker: {
|
picker: {
|
||||||
item: {
|
item: {
|
||||||
...picker(theme).item,
|
...picker(colorScheme).item,
|
||||||
margin: { left: sideMargin, right: sideMargin }
|
margin: { left: sideMargin, right: sideMargin }
|
||||||
},
|
},
|
||||||
empty: picker(theme).empty,
|
empty: picker(colorScheme).empty,
|
||||||
inputEditor: {
|
inputEditor: {
|
||||||
background: backgroundColor(theme, 500),
|
background: background(layer, "on"),
|
||||||
cornerRadius: 6,
|
cornerRadius: 6,
|
||||||
text: text(theme, "mono", "primary"),
|
text: text(layer, "mono",),
|
||||||
placeholderText: text(theme, "mono", "placeholder", { size: "sm" }),
|
placeholderText: text(layer, "mono", "variant", { size: "sm" }),
|
||||||
selection: player(theme, 1).selection,
|
selection: colorScheme.players[0],
|
||||||
border: border(theme, "secondary"),
|
border: border(layer),
|
||||||
padding: {
|
padding: {
|
||||||
bottom: 4,
|
bottom: 4,
|
||||||
left: 8,
|
left: 8,
|
||||||
|
@ -51,13 +53,13 @@ export default function contactFinder(theme: Theme) {
|
||||||
contactButton: {
|
contactButton: {
|
||||||
...contactButton,
|
...contactButton,
|
||||||
hover: {
|
hover: {
|
||||||
background: backgroundColor(theme, 100, "hovered"),
|
background: background(layer, "variant", "hovered"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
disabledContactButton: {
|
disabledContactButton: {
|
||||||
...contactButton,
|
...contactButton,
|
||||||
background: backgroundColor(theme, 100),
|
background: background(layer, "disabled"),
|
||||||
color: iconColor(theme, "muted"),
|
color: foreground(layer, "disabled"),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,21 @@
|
||||||
import Theme from "../themes/common/theme";
|
import { ColorScheme } from "../themes/common/colorScheme";
|
||||||
import { backgroundColor, border, borderColor, iconColor, player, text } from "./components";
|
import {
|
||||||
|
background,
|
||||||
|
border,
|
||||||
|
borderColor,
|
||||||
|
foreground,
|
||||||
|
text,
|
||||||
|
} from "./components";
|
||||||
|
|
||||||
export default function contactList(theme: Theme) {
|
export default function contactsPanel(colorScheme: ColorScheme) {
|
||||||
const nameMargin = 8;
|
const nameMargin = 8;
|
||||||
const sidePadding = 12;
|
const sidePadding = 12;
|
||||||
|
|
||||||
|
let layer = colorScheme.middle;
|
||||||
|
|
||||||
const contactButton = {
|
const contactButton = {
|
||||||
background: backgroundColor(theme, 100),
|
background: background(layer, "on"),
|
||||||
color: iconColor(theme, "primary"),
|
color: foreground(layer, "on"),
|
||||||
iconWidth: 8,
|
iconWidth: 8,
|
||||||
buttonWidth: 16,
|
buttonWidth: 16,
|
||||||
cornerRadius: 8,
|
cornerRadius: 8,
|
||||||
|
@ -20,7 +28,7 @@ export default function contactList(theme: Theme) {
|
||||||
width: 14,
|
width: 14,
|
||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
...text(theme, "mono", "placeholder", { size: "sm" }),
|
...text(layer, "mono", { size: "sm" }),
|
||||||
margin: {
|
margin: {
|
||||||
left: nameMargin,
|
left: nameMargin,
|
||||||
right: 6,
|
right: 6,
|
||||||
|
@ -39,13 +47,15 @@ export default function contactList(theme: Theme) {
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
background: background(layer),
|
||||||
|
padding: { top: 12, bottom: 0 },
|
||||||
userQueryEditor: {
|
userQueryEditor: {
|
||||||
background: backgroundColor(theme, 500),
|
background: background(layer, "on"),
|
||||||
cornerRadius: 6,
|
cornerRadius: 6,
|
||||||
text: text(theme, "mono", "primary"),
|
text: text(layer, "mono", "on"),
|
||||||
placeholderText: text(theme, "mono", "placeholder", { size: "sm" }),
|
placeholderText: text(layer, "mono", "on", "disabled", { size: "sm" }),
|
||||||
selection: player(theme, 1).selection,
|
selection: colorScheme.players[0],
|
||||||
border: border(theme, "secondary"),
|
border: border(layer, "on"),
|
||||||
padding: {
|
padding: {
|
||||||
bottom: 4,
|
bottom: 4,
|
||||||
left: 8,
|
left: 8,
|
||||||
|
@ -58,27 +68,28 @@ export default function contactList(theme: Theme) {
|
||||||
},
|
},
|
||||||
userQueryEditorHeight: 33,
|
userQueryEditorHeight: 33,
|
||||||
addContactButton: {
|
addContactButton: {
|
||||||
color: iconColor(theme, "primary"),
|
margin: { left: 6, right: 12 },
|
||||||
|
color: foreground(layer, "on"),
|
||||||
buttonWidth: 28,
|
buttonWidth: 28,
|
||||||
iconWidth: 16,
|
iconWidth: 16,
|
||||||
},
|
},
|
||||||
rowHeight: 28,
|
rowHeight: 28,
|
||||||
sectionIconSize: 8,
|
sectionIconSize: 8,
|
||||||
headerRow: {
|
headerRow: {
|
||||||
...text(theme, "mono", "secondary", { size: "sm" }),
|
...text(layer, "mono", { size: "sm" }),
|
||||||
margin: { top: 6 },
|
margin: { top: 14 },
|
||||||
padding: {
|
padding: {
|
||||||
left: sidePadding,
|
left: sidePadding,
|
||||||
right: sidePadding,
|
right: sidePadding,
|
||||||
},
|
},
|
||||||
active: {
|
active: {
|
||||||
...text(theme, "mono", "primary", { size: "sm" }),
|
...text(layer, "mono", "active", { size: "sm" }),
|
||||||
background: backgroundColor(theme, 100, "active"),
|
background: background(layer, "active"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
leaveCall: {
|
leaveCall: {
|
||||||
background: backgroundColor(theme, 100),
|
background: background(layer),
|
||||||
border: border(theme, "secondary"),
|
border: border(layer),
|
||||||
cornerRadius: 6,
|
cornerRadius: 6,
|
||||||
margin: {
|
margin: {
|
||||||
top: 1,
|
top: 1,
|
||||||
|
@ -89,11 +100,11 @@ export default function contactList(theme: Theme) {
|
||||||
left: 7,
|
left: 7,
|
||||||
right: 7,
|
right: 7,
|
||||||
},
|
},
|
||||||
...text(theme, "sans", "secondary", { size: "xs" }),
|
...text(layer, "sans", "variant", { size: "xs" }),
|
||||||
hover: {
|
hover: {
|
||||||
...text(theme, "sans", "active", { size: "xs" }),
|
...text(layer, "sans", "hovered", { size: "xs" }),
|
||||||
background: backgroundColor(theme, "on300", "hovered"),
|
background: background(layer, "hovered"),
|
||||||
border: border(theme, "primary"),
|
border: border(layer, "hovered"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
contactRow: {
|
contactRow: {
|
||||||
|
@ -102,7 +113,7 @@ export default function contactList(theme: Theme) {
|
||||||
right: sidePadding,
|
right: sidePadding,
|
||||||
},
|
},
|
||||||
active: {
|
active: {
|
||||||
background: backgroundColor(theme, 100, "active"),
|
background: background(layer, "active"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
contactAvatar: {
|
contactAvatar: {
|
||||||
|
@ -113,16 +124,16 @@ export default function contactList(theme: Theme) {
|
||||||
cornerRadius: 4,
|
cornerRadius: 4,
|
||||||
padding: 4,
|
padding: 4,
|
||||||
margin: { top: 12, left: 12 },
|
margin: { top: 12, left: 12 },
|
||||||
background: iconColor(theme, "ok"),
|
background: foreground(layer, "positive"),
|
||||||
},
|
},
|
||||||
contactStatusBusy: {
|
contactStatusBusy: {
|
||||||
cornerRadius: 4,
|
cornerRadius: 4,
|
||||||
padding: 4,
|
padding: 4,
|
||||||
margin: { top: 12, left: 12 },
|
margin: { top: 12, left: 12 },
|
||||||
background: iconColor(theme, "error"),
|
background: foreground(layer, "negative"),
|
||||||
},
|
},
|
||||||
contactUsername: {
|
contactUsername: {
|
||||||
...text(theme, "mono", "primary", { size: "sm" }),
|
...text(layer, "mono", { size: "sm" }),
|
||||||
margin: {
|
margin: {
|
||||||
left: nameMargin,
|
left: nameMargin,
|
||||||
},
|
},
|
||||||
|
@ -131,39 +142,39 @@ export default function contactList(theme: Theme) {
|
||||||
contactButton: {
|
contactButton: {
|
||||||
...contactButton,
|
...contactButton,
|
||||||
hover: {
|
hover: {
|
||||||
background: backgroundColor(theme, "on300", "hovered"),
|
background: background(layer, "hovered"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
disabledButton: {
|
disabledButton: {
|
||||||
...contactButton,
|
...contactButton,
|
||||||
background: backgroundColor(theme, 100),
|
background: background(layer, "on"),
|
||||||
color: iconColor(theme, "muted"),
|
color: foreground(layer, "on"),
|
||||||
},
|
},
|
||||||
callingIndicator: {
|
callingIndicator: {
|
||||||
...text(theme, "mono", "muted", { size: "xs" })
|
...text(layer, "mono", "variant", { size: "xs" })
|
||||||
},
|
},
|
||||||
treeBranch: {
|
treeBranch: {
|
||||||
color: borderColor(theme, "active"),
|
color: borderColor(layer),
|
||||||
width: 1,
|
width: 1,
|
||||||
hover: {
|
hover: {
|
||||||
color: borderColor(theme, "active"),
|
color: borderColor(layer),
|
||||||
},
|
},
|
||||||
active: {
|
active: {
|
||||||
color: borderColor(theme, "active"),
|
color: borderColor(layer),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
projectRow: {
|
projectRow: {
|
||||||
...projectRow,
|
...projectRow,
|
||||||
background: backgroundColor(theme, 300),
|
background: background(layer, "on"),
|
||||||
name: {
|
name: {
|
||||||
...projectRow.name,
|
...projectRow.name,
|
||||||
...text(theme, "mono", "secondary", { size: "sm" }),
|
...text(layer, "mono", { size: "sm" }),
|
||||||
},
|
},
|
||||||
hover: {
|
hover: {
|
||||||
background: backgroundColor(theme, 300, "hovered"),
|
background: background(layer, "on", "hovered"),
|
||||||
},
|
},
|
||||||
active: {
|
active: {
|
||||||
background: backgroundColor(theme, 300, "active"),
|
background: background(layer, "on", "active"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import Theme from "../themes/common/theme";
|
import { ColorScheme } from "../themes/common/colorScheme";
|
||||||
import { backgroundColor, iconColor, text } from "./components";
|
import { background, foreground, text } from "./components";
|
||||||
|
|
||||||
const avatarSize = 12;
|
const avatarSize = 12;
|
||||||
const headerPadding = 8;
|
const headerPadding = 8;
|
||||||
|
|
||||||
export default function contactNotification(theme: Theme): Object {
|
export default function contactNotification(colorScheme: ColorScheme): Object {
|
||||||
|
let layer = colorScheme.lowest;
|
||||||
return {
|
return {
|
||||||
headerAvatar: {
|
headerAvatar: {
|
||||||
height: avatarSize,
|
height: avatarSize,
|
||||||
|
@ -12,32 +13,32 @@ export default function contactNotification(theme: Theme): Object {
|
||||||
cornerRadius: 6,
|
cornerRadius: 6,
|
||||||
},
|
},
|
||||||
headerMessage: {
|
headerMessage: {
|
||||||
...text(theme, "sans", "primary", { size: "xs" }),
|
...text(layer, "sans", { size: "xs" }),
|
||||||
margin: { left: headerPadding, right: headerPadding },
|
margin: { left: headerPadding, right: headerPadding },
|
||||||
},
|
},
|
||||||
headerHeight: 18,
|
headerHeight: 18,
|
||||||
bodyMessage: {
|
bodyMessage: {
|
||||||
...text(theme, "sans", "secondary", { size: "xs" }),
|
...text(layer, "sans", { size: "xs" }),
|
||||||
margin: { left: avatarSize + headerPadding, top: 6, bottom: 6 },
|
margin: { left: avatarSize + headerPadding, top: 6, bottom: 6 },
|
||||||
},
|
},
|
||||||
button: {
|
button: {
|
||||||
...text(theme, "sans", "primary", { size: "xs" }),
|
...text(layer, "sans", "on", { size: "xs" }),
|
||||||
background: backgroundColor(theme, "on300"),
|
background: background(layer, "on"),
|
||||||
padding: 4,
|
padding: 4,
|
||||||
cornerRadius: 6,
|
cornerRadius: 6,
|
||||||
margin: { left: 6 },
|
margin: { left: 6 },
|
||||||
hover: {
|
hover: {
|
||||||
background: backgroundColor(theme, "on300", "hovered"),
|
background: background(layer, "on", "hovered"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
dismissButton: {
|
dismissButton: {
|
||||||
color: iconColor(theme, "secondary"),
|
color: foreground(layer, "on"),
|
||||||
iconWidth: 8,
|
iconWidth: 8,
|
||||||
iconHeight: 8,
|
iconHeight: 8,
|
||||||
buttonWidth: 8,
|
buttonWidth: 8,
|
||||||
buttonHeight: 8,
|
buttonHeight: 8,
|
||||||
hover: {
|
hover: {
|
||||||
color: iconColor(theme, "primary"),
|
color: foreground(layer, "on", "hovered"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
import Theme from "../themes/common/theme";
|
import { ColorScheme } from "../themes/common/colorScheme";
|
||||||
import { backgroundColor, border, borderColor, popoverShadow, text } from "./components";
|
import { background, border, text } from "./components";
|
||||||
|
|
||||||
export default function contactsPopover(theme: Theme) {
|
export default function contactsPopover(colorScheme: ColorScheme) {
|
||||||
|
let layer = colorScheme.middle;
|
||||||
const sidePadding = 12;
|
const sidePadding = 12;
|
||||||
return {
|
return {
|
||||||
background: backgroundColor(theme, 300, "base"),
|
background: background(layer),
|
||||||
cornerRadius: 6,
|
cornerRadius: 6,
|
||||||
padding: { top: 6 },
|
padding: { top: 6 },
|
||||||
margin: { top: -6 },
|
margin: { top: -6 },
|
||||||
shadow: popoverShadow(theme),
|
shadow: colorScheme.popoverShadow,
|
||||||
border: border(theme, "primary"),
|
border: border(layer),
|
||||||
width: 300,
|
width: 300,
|
||||||
height: 400,
|
height: 400,
|
||||||
inviteRowHeight: 28,
|
inviteRowHeight: 28,
|
||||||
|
@ -18,10 +19,10 @@ export default function contactsPopover(theme: Theme) {
|
||||||
left: sidePadding,
|
left: sidePadding,
|
||||||
right: sidePadding,
|
right: sidePadding,
|
||||||
},
|
},
|
||||||
border: { top: true, width: 1, color: borderColor(theme, "primary") },
|
border: border(layer, { top: true }),
|
||||||
text: text(theme, "sans", "secondary", { size: "sm" }),
|
text: text(layer, "sans", "variant", { size: "sm" }),
|
||||||
hover: {
|
hover: {
|
||||||
text: text(theme, "sans", "active", { size: "sm" }),
|
text: text(layer, "sans", "hovered", { size: "sm" }),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,45 +1,40 @@
|
||||||
import Theme from "../themes/common/theme";
|
import { ColorScheme } from "../themes/common/colorScheme";
|
||||||
import {
|
import { background, border, borderColor, text } from "./components";
|
||||||
backgroundColor,
|
|
||||||
border,
|
|
||||||
borderColor,
|
|
||||||
popoverShadow,
|
|
||||||
text,
|
|
||||||
} from "./components";
|
|
||||||
|
|
||||||
export default function contextMenu(theme: Theme) {
|
export default function contextMenu(colorScheme: ColorScheme) {
|
||||||
|
let layer = colorScheme.middle;
|
||||||
return {
|
return {
|
||||||
background: backgroundColor(theme, 300, "base"),
|
background: background(layer),
|
||||||
cornerRadius: 6,
|
cornerRadius: 10,
|
||||||
padding: 6,
|
padding: 4,
|
||||||
shadow: popoverShadow(theme),
|
shadow: colorScheme.popoverShadow,
|
||||||
border: border(theme, "primary"),
|
border: border(layer),
|
||||||
keystrokeMargin: 30,
|
keystrokeMargin: 30,
|
||||||
item: {
|
item: {
|
||||||
iconSpacing: 8,
|
iconSpacing: 8,
|
||||||
iconWidth: 14,
|
iconWidth: 14,
|
||||||
padding: { left: 4, right: 4, top: 2, bottom: 2 },
|
padding: { left: 6, right: 6, top: 2, bottom: 2 },
|
||||||
cornerRadius: 6,
|
cornerRadius: 6,
|
||||||
label: text(theme, "sans", "primary", { size: "sm" }),
|
label: text(layer, "sans", { size: "sm" }),
|
||||||
keystroke: {
|
keystroke: {
|
||||||
...text(theme, "sans", "muted", { size: "sm", weight: "bold" }),
|
...text(layer, "sans", "variant", { size: "sm", weight: "bold" }),
|
||||||
padding: { left: 3, right: 3 },
|
padding: { left: 3, right: 3 },
|
||||||
},
|
},
|
||||||
hover: {
|
hover: {
|
||||||
background: backgroundColor(theme, 300, "hovered"),
|
background: background(layer, "hovered"),
|
||||||
text: text(theme, "sans", "primary", { size: "sm" }),
|
label: text(layer, "sans", "hovered", { size: "sm" }),
|
||||||
},
|
},
|
||||||
active: {
|
active: {
|
||||||
background: backgroundColor(theme, 300, "active"),
|
background: background(layer, "active"),
|
||||||
text: text(theme, "sans", "active", { size: "sm" }),
|
label: text(layer, "sans", "active", { size: "sm" }),
|
||||||
},
|
},
|
||||||
activeHover: {
|
activeHover: {
|
||||||
background: backgroundColor(theme, 300, "hovered"),
|
background: background(layer, "active"),
|
||||||
text: text(theme, "sans", "active", { size: "sm" }),
|
label: text(layer, "sans", "active", { size: "sm" }),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
separator: {
|
separator: {
|
||||||
background: borderColor(theme, "primary"),
|
background: borderColor(layer),
|
||||||
margin: { top: 2, bottom: 2 },
|
margin: { top: 2, bottom: 2 },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,19 +1,22 @@
|
||||||
import Theme from "../themes/common/theme";
|
import { fontWeights } from "../common";
|
||||||
import { withOpacity } from "../utils/color";
|
import { withOpacity } from "../utils/color";
|
||||||
import {
|
import {
|
||||||
backgroundColor,
|
ColorScheme,
|
||||||
|
Layer,
|
||||||
|
StyleSets,
|
||||||
|
} from "../themes/common/colorScheme";
|
||||||
|
import {
|
||||||
|
background,
|
||||||
border,
|
border,
|
||||||
borderColor,
|
borderColor,
|
||||||
iconColor,
|
foreground,
|
||||||
player,
|
|
||||||
popoverShadow,
|
|
||||||
text,
|
text,
|
||||||
textColor,
|
|
||||||
TextColor,
|
|
||||||
} from "./components";
|
} from "./components";
|
||||||
import hoverPopover from "./hoverPopover";
|
import hoverPopover from "./hoverPopover";
|
||||||
|
|
||||||
export default function editor(theme: Theme) {
|
export default function editor(colorScheme: ColorScheme) {
|
||||||
|
let layer = colorScheme.highest;
|
||||||
|
|
||||||
const autocompleteItem = {
|
const autocompleteItem = {
|
||||||
cornerRadius: 6,
|
cornerRadius: 6,
|
||||||
padding: {
|
padding: {
|
||||||
|
@ -24,17 +27,17 @@ export default function editor(theme: Theme) {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function diagnostic(theme: Theme, color: TextColor) {
|
function diagnostic(layer: Layer, styleSet: StyleSets) {
|
||||||
return {
|
return {
|
||||||
textScaleFactor: 0.857,
|
textScaleFactor: 0.857,
|
||||||
header: {
|
header: {
|
||||||
border: border(theme, "primary", {
|
border: border(layer, {
|
||||||
top: true,
|
top: true,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
message: {
|
message: {
|
||||||
text: text(theme, "sans", color, { size: "sm" }),
|
text: text(layer, "sans", styleSet, "inverted", { size: "sm" }),
|
||||||
highlightText: text(theme, "sans", color, {
|
highlightText: text(layer, "sans", styleSet, "inverted", {
|
||||||
size: "sm",
|
size: "sm",
|
||||||
weight: "bold",
|
weight: "bold",
|
||||||
}),
|
}),
|
||||||
|
@ -42,121 +45,207 @@ export default function editor(theme: Theme) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const syntax: any = {};
|
const syntax = {
|
||||||
for (const syntaxKey in theme.syntax) {
|
primary: {
|
||||||
const style = theme.syntax[syntaxKey];
|
color: colorScheme.ramps.neutral(1).hex(),
|
||||||
syntax[syntaxKey] = {
|
weight: fontWeights.normal,
|
||||||
color: style.color,
|
},
|
||||||
weight: style.weight,
|
comment: {
|
||||||
underline: style.underline,
|
color: colorScheme.ramps.neutral(0.71).hex(),
|
||||||
italic: style.italic,
|
weight: fontWeights.normal,
|
||||||
};
|
},
|
||||||
}
|
punctuation: {
|
||||||
|
color: colorScheme.ramps.neutral(0.86).hex(),
|
||||||
|
weight: fontWeights.normal,
|
||||||
|
},
|
||||||
|
constant: {
|
||||||
|
color: colorScheme.ramps.green(0.5).hex(),
|
||||||
|
weight: fontWeights.normal,
|
||||||
|
},
|
||||||
|
keyword: {
|
||||||
|
color: colorScheme.ramps.blue(0.5).hex(),
|
||||||
|
weight: fontWeights.normal,
|
||||||
|
},
|
||||||
|
function: {
|
||||||
|
color: colorScheme.ramps.yellow(0.5).hex(),
|
||||||
|
weight: fontWeights.normal,
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
color: colorScheme.ramps.cyan(0.5).hex(),
|
||||||
|
weight: fontWeights.normal,
|
||||||
|
},
|
||||||
|
constructor: {
|
||||||
|
color: colorScheme.ramps.blue(0.5).hex(),
|
||||||
|
weight: fontWeights.normal,
|
||||||
|
},
|
||||||
|
variant: {
|
||||||
|
color: colorScheme.ramps.blue(0.5).hex(),
|
||||||
|
weight: fontWeights.normal,
|
||||||
|
},
|
||||||
|
property: {
|
||||||
|
color: colorScheme.ramps.blue(0.5).hex(),
|
||||||
|
weight: fontWeights.normal,
|
||||||
|
},
|
||||||
|
enum: {
|
||||||
|
color: colorScheme.ramps.orange(0.5).hex(),
|
||||||
|
weight: fontWeights.normal,
|
||||||
|
},
|
||||||
|
operator: {
|
||||||
|
color: colorScheme.ramps.orange(0.5).hex(),
|
||||||
|
weight: fontWeights.normal,
|
||||||
|
},
|
||||||
|
string: {
|
||||||
|
color: colorScheme.ramps.orange(0.5).hex(),
|
||||||
|
weight: fontWeights.normal,
|
||||||
|
},
|
||||||
|
number: {
|
||||||
|
color: colorScheme.ramps.green(0.5).hex(),
|
||||||
|
weight: fontWeights.normal,
|
||||||
|
},
|
||||||
|
boolean: {
|
||||||
|
color: colorScheme.ramps.green(0.5).hex(),
|
||||||
|
weight: fontWeights.normal,
|
||||||
|
},
|
||||||
|
predictive: {
|
||||||
|
color: colorScheme.ramps.neutral(0.57).hex(),
|
||||||
|
weight: fontWeights.normal,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
color: colorScheme.ramps.yellow(0.5).hex(),
|
||||||
|
weight: fontWeights.bold,
|
||||||
|
},
|
||||||
|
emphasis: {
|
||||||
|
color: colorScheme.ramps.blue(0.5).hex(),
|
||||||
|
weight: fontWeights.normal,
|
||||||
|
},
|
||||||
|
"emphasis.strong": {
|
||||||
|
color: colorScheme.ramps.blue(0.5).hex(),
|
||||||
|
weight: fontWeights.bold,
|
||||||
|
},
|
||||||
|
linkUri: {
|
||||||
|
color: colorScheme.ramps.green(0.5).hex(),
|
||||||
|
weight: fontWeights.normal,
|
||||||
|
underline: true,
|
||||||
|
},
|
||||||
|
linkText: {
|
||||||
|
color: colorScheme.ramps.orange(0.5).hex(),
|
||||||
|
weight: fontWeights.normal,
|
||||||
|
italic: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
textColor: theme.syntax.primary.color,
|
textColor: syntax.primary.color,
|
||||||
background: backgroundColor(theme, 500),
|
background: background(layer),
|
||||||
activeLineBackground: theme.editor.line.active,
|
activeLineBackground: background(layer, "on"),
|
||||||
|
highlightedLineBackground: background(layer, "on"),
|
||||||
codeActions: {
|
codeActions: {
|
||||||
indicator: iconColor(theme, "secondary"),
|
indicator: foreground(layer, "variant"),
|
||||||
verticalScale: 0.618
|
verticalScale: 0.55,
|
||||||
},
|
},
|
||||||
diff: {
|
diff: {
|
||||||
deleted: theme.iconColor.error,
|
deleted: foreground(layer, "negative"),
|
||||||
inserted: theme.iconColor.ok,
|
modified: foreground(layer, "warning"),
|
||||||
modified: theme.iconColor.warning,
|
inserted: foreground(layer, "positive"),
|
||||||
removedWidthEm: 0.275,
|
removedWidthEm: 0.275,
|
||||||
widthEm: 0.16,
|
widthEm: 0.16,
|
||||||
cornerRadius: 0.05,
|
cornerRadius: 0.05,
|
||||||
},
|
},
|
||||||
documentHighlightReadBackground: theme.editor.highlight.occurrence,
|
documentHighlightReadBackground: colorScheme.ramps
|
||||||
documentHighlightWriteBackground: theme.editor.highlight.activeOccurrence,
|
.neutral(0.5)
|
||||||
errorColor: theme.textColor.error,
|
.alpha(0.2)
|
||||||
gutterBackground: backgroundColor(theme, 500),
|
.hex(), // TODO: This was blend
|
||||||
|
documentHighlightWriteBackground: colorScheme.ramps
|
||||||
|
.neutral(0.5)
|
||||||
|
.alpha(0.4)
|
||||||
|
.hex(), // TODO: This was blend * 2
|
||||||
|
errorColor: background(layer, "negative"),
|
||||||
|
gutterBackground: background(layer),
|
||||||
gutterPaddingFactor: 3.5,
|
gutterPaddingFactor: 3.5,
|
||||||
highlightedLineBackground: theme.editor.line.highlighted,
|
lineNumber: foreground(layer, "disabled"),
|
||||||
lineNumber: theme.editor.gutter.primary,
|
lineNumberActive: foreground(layer),
|
||||||
lineNumberActive: theme.editor.gutter.active,
|
|
||||||
renameFade: 0.6,
|
renameFade: 0.6,
|
||||||
unnecessaryCodeFade: 0.5,
|
unnecessaryCodeFade: 0.5,
|
||||||
selection: player(theme, 1).selection,
|
selection: colorScheme.players[0],
|
||||||
guestSelections: [
|
guestSelections: [
|
||||||
player(theme, 2).selection,
|
colorScheme.players[1],
|
||||||
player(theme, 3).selection,
|
colorScheme.players[2],
|
||||||
player(theme, 4).selection,
|
colorScheme.players[3],
|
||||||
player(theme, 5).selection,
|
colorScheme.players[4],
|
||||||
player(theme, 6).selection,
|
colorScheme.players[5],
|
||||||
player(theme, 7).selection,
|
colorScheme.players[6],
|
||||||
player(theme, 8).selection,
|
colorScheme.players[7],
|
||||||
],
|
],
|
||||||
autocomplete: {
|
autocomplete: {
|
||||||
background: backgroundColor(theme, 500),
|
background: background(colorScheme.middle),
|
||||||
cornerRadius: 8,
|
cornerRadius: 8,
|
||||||
padding: 4,
|
padding: 4,
|
||||||
border: border(theme, "secondary"),
|
|
||||||
shadow: popoverShadow(theme),
|
|
||||||
item: autocompleteItem,
|
|
||||||
hoveredItem: {
|
|
||||||
...autocompleteItem,
|
|
||||||
background: backgroundColor(theme, 500, "hovered"),
|
|
||||||
},
|
|
||||||
margin: {
|
margin: {
|
||||||
left: -14,
|
left: -14,
|
||||||
},
|
},
|
||||||
matchHighlight: text(theme, "mono", "feature"),
|
border: border(colorScheme.middle),
|
||||||
|
shadow: colorScheme.popoverShadow,
|
||||||
|
matchHighlight: foreground(colorScheme.middle, "accent"),
|
||||||
|
item: autocompleteItem,
|
||||||
|
hoveredItem: {
|
||||||
|
...autocompleteItem,
|
||||||
|
matchHighlight: foreground(colorScheme.middle, "accent", "hovered"),
|
||||||
|
background: background(colorScheme.middle, "hovered"),
|
||||||
|
},
|
||||||
selectedItem: {
|
selectedItem: {
|
||||||
...autocompleteItem,
|
...autocompleteItem,
|
||||||
background: backgroundColor(theme, 500, "active"),
|
matchHighlight: foreground(colorScheme.middle, "accent", "active"),
|
||||||
|
background: background(colorScheme.middle, "active"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
diagnosticHeader: {
|
diagnosticHeader: {
|
||||||
background: backgroundColor(theme, 300),
|
background: background(colorScheme.middle),
|
||||||
iconWidthFactor: 1.5,
|
iconWidthFactor: 1.5,
|
||||||
textScaleFactor: 0.857, // NateQ: Will we need dynamic sizing for text? If so let's create tokens for these.
|
textScaleFactor: 0.857,
|
||||||
border: border(theme, "secondary", {
|
border: border(colorScheme.middle, {
|
||||||
bottom: true,
|
bottom: true,
|
||||||
top: true,
|
top: true,
|
||||||
}),
|
}),
|
||||||
code: {
|
code: {
|
||||||
...text(theme, "mono", "secondary", { size: "sm" }),
|
...text(colorScheme.middle, "mono", { size: "sm" }),
|
||||||
margin: {
|
margin: {
|
||||||
left: 10,
|
left: 10,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
message: {
|
message: {
|
||||||
highlightText: text(theme, "sans", "primary", {
|
highlightText: text(colorScheme.middle, "sans", {
|
||||||
size: "sm",
|
size: "sm",
|
||||||
weight: "bold",
|
weight: "bold",
|
||||||
}),
|
}),
|
||||||
text: text(theme, "sans", "secondary", { size: "sm" }),
|
text: text(colorScheme.middle, "sans", { size: "sm" }),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
diagnosticPathHeader: {
|
diagnosticPathHeader: {
|
||||||
background: theme.editor.line.active,
|
background: background(colorScheme.middle),
|
||||||
textScaleFactor: 0.857,
|
textScaleFactor: 0.857,
|
||||||
filename: text(theme, "mono", "primary", { size: "sm" }),
|
filename: text(colorScheme.middle, "mono", { size: "sm" }),
|
||||||
path: {
|
path: {
|
||||||
...text(theme, "mono", "muted", { size: "sm" }),
|
...text(colorScheme.middle, "mono", { size: "sm" }),
|
||||||
margin: {
|
margin: {
|
||||||
left: 12,
|
left: 12,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
errorDiagnostic: diagnostic(theme, "error"),
|
errorDiagnostic: diagnostic(colorScheme.middle, "negative"),
|
||||||
warningDiagnostic: diagnostic(theme, "warning"),
|
warningDiagnostic: diagnostic(colorScheme.middle, "warning"),
|
||||||
informationDiagnostic: diagnostic(theme, "info"),
|
informationDiagnostic: diagnostic(colorScheme.middle, "accent"),
|
||||||
hintDiagnostic: diagnostic(theme, "info"),
|
hintDiagnostic: diagnostic(colorScheme.middle, "warning"),
|
||||||
invalidErrorDiagnostic: diagnostic(theme, "secondary"),
|
invalidErrorDiagnostic: diagnostic(colorScheme.middle, "base"),
|
||||||
invalidHintDiagnostic: diagnostic(theme, "secondary"),
|
invalidHintDiagnostic: diagnostic(colorScheme.middle, "base"),
|
||||||
invalidInformationDiagnostic: diagnostic(theme, "secondary"),
|
invalidInformationDiagnostic: diagnostic(colorScheme.middle, "base"),
|
||||||
invalidWarningDiagnostic: diagnostic(theme, "secondary"),
|
invalidWarningDiagnostic: diagnostic(colorScheme.middle, "base"),
|
||||||
hoverPopover: hoverPopover(theme),
|
hoverPopover: hoverPopover(colorScheme),
|
||||||
linkDefinition: {
|
linkDefinition: {
|
||||||
color: theme.syntax.linkUri.color,
|
color: syntax.linkUri.color,
|
||||||
underline: theme.syntax.linkUri.underline,
|
underline: syntax.linkUri.underline,
|
||||||
},
|
},
|
||||||
jumpIcon: {
|
jumpIcon: {
|
||||||
color: iconColor(theme, "secondary"),
|
color: foreground(layer, "on"),
|
||||||
iconWidth: 20,
|
iconWidth: 20,
|
||||||
buttonWidth: 20,
|
buttonWidth: 20,
|
||||||
cornerRadius: 6,
|
cornerRadius: 6,
|
||||||
|
@ -167,32 +256,28 @@ export default function editor(theme: Theme) {
|
||||||
right: 6,
|
right: 6,
|
||||||
},
|
},
|
||||||
hover: {
|
hover: {
|
||||||
color: iconColor(theme, "active"),
|
color: foreground(layer, "on", "hovered"),
|
||||||
background: backgroundColor(theme, "on500"),
|
background: background(layer, "on", "hovered"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
scrollbar: {
|
scrollbar: {
|
||||||
width: 12,
|
width: 12,
|
||||||
minHeightFactor: 1.0,
|
minHeightFactor: 1.0,
|
||||||
track: {
|
track: {
|
||||||
border: {
|
border: border(layer, "variant", { left: true }),
|
||||||
left: true,
|
|
||||||
width: 1,
|
|
||||||
color: borderColor(theme, "secondary"),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
thumb: {
|
thumb: {
|
||||||
background: withOpacity(borderColor(theme, "secondary"), 0.5),
|
background: withOpacity(borderColor(layer, "variant"), 0.5),
|
||||||
border: {
|
border: {
|
||||||
width: 1,
|
width: 1,
|
||||||
color: withOpacity(borderColor(theme, 'muted'), 0.5),
|
color: withOpacity(borderColor(layer, 'variant'), 0.5),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
compositionMark: {
|
compositionMark: {
|
||||||
underline: {
|
underline: {
|
||||||
thickness: 1.0,
|
thickness: 1.0,
|
||||||
color: borderColor(theme, "active")
|
color: borderColor(layer),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
syntax,
|
syntax,
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
import Theme from "../themes/common/theme";
|
import { ColorScheme } from "../themes/common/colorScheme";
|
||||||
import { backgroundColor, border, popoverShadow, text } from "./components";
|
import { background, border, text } from "./components";
|
||||||
|
|
||||||
export default function HoverPopover(theme: Theme) {
|
export default function HoverPopover(colorScheme: ColorScheme) {
|
||||||
|
let layer = colorScheme.middle;
|
||||||
let baseContainer = {
|
let baseContainer = {
|
||||||
background: backgroundColor(theme, "on500"),
|
background: background(layer),
|
||||||
cornerRadius: 8,
|
cornerRadius: 8,
|
||||||
padding: {
|
padding: {
|
||||||
left: 8,
|
left: 8,
|
||||||
right: 8,
|
right: 8,
|
||||||
top: 4,
|
top: 4,
|
||||||
bottom: 4
|
bottom: 4,
|
||||||
},
|
},
|
||||||
shadow: popoverShadow(theme),
|
shadow: colorScheme.popoverShadow,
|
||||||
border: border(theme, "secondary"),
|
border: border(layer),
|
||||||
margin: {
|
margin: {
|
||||||
left: -8,
|
left: -8,
|
||||||
},
|
},
|
||||||
|
@ -22,32 +23,23 @@ export default function HoverPopover(theme: Theme) {
|
||||||
container: baseContainer,
|
container: baseContainer,
|
||||||
infoContainer: {
|
infoContainer: {
|
||||||
...baseContainer,
|
...baseContainer,
|
||||||
background: backgroundColor(theme, "on500Info"),
|
background: background(layer, "accent"),
|
||||||
border: {
|
border: border(layer, "accent"),
|
||||||
color: theme.ramps.blue(0).hex(),
|
|
||||||
width: 1,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
warningContainer: {
|
warningContainer: {
|
||||||
...baseContainer,
|
...baseContainer,
|
||||||
background: backgroundColor(theme, "on500Warning"),
|
background: background(layer, "warning"),
|
||||||
border: {
|
border: border(layer, "warning"),
|
||||||
color: theme.ramps.yellow(0).hex(),
|
|
||||||
width: 1,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
errorContainer: {
|
errorContainer: {
|
||||||
...baseContainer,
|
...baseContainer,
|
||||||
background: backgroundColor(theme, "on500Error"),
|
background: background(layer, "negative"),
|
||||||
border: {
|
border: border(layer, "negative"),
|
||||||
color: theme.ramps.red(0).hex(),
|
|
||||||
width: 1,
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
block_style: {
|
block_style: {
|
||||||
padding: { top: 4 },
|
padding: { top: 4 },
|
||||||
},
|
},
|
||||||
prose: text(theme, "sans", "primary", { size: "sm" }),
|
prose: text(layer, "sans", { size: "sm" }),
|
||||||
highlight: theme.editor.highlight.occurrence,
|
highlight: colorScheme.ramps.neutral(0.5).alpha(0.2).hex(), // TODO: blend was used here. Replace with something better
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import Theme from "../themes/common/theme";
|
import { ColorScheme } from "../themes/common/colorScheme";
|
||||||
import { backgroundColor, borderColor, text } from "./components";
|
import { background, border, text } from "./components";
|
||||||
|
|
||||||
export default function incomingCallNotification(theme: Theme): Object {
|
export default function incomingCallNotification(colorScheme: ColorScheme): Object {
|
||||||
|
let layer = colorScheme.middle;
|
||||||
const avatarSize = 48;
|
const avatarSize = 48;
|
||||||
return {
|
return {
|
||||||
windowHeight: 74,
|
windowHeight: 74,
|
||||||
windowWidth: 380,
|
windowWidth: 380,
|
||||||
background: backgroundColor(theme, 300),
|
background: background(layer),
|
||||||
callerContainer: {
|
callerContainer: {
|
||||||
padding: 12,
|
padding: 12,
|
||||||
},
|
},
|
||||||
|
@ -19,26 +20,26 @@ export default function incomingCallNotification(theme: Theme): Object {
|
||||||
margin: { left: 10 },
|
margin: { left: 10 },
|
||||||
},
|
},
|
||||||
callerUsername: {
|
callerUsername: {
|
||||||
...text(theme, "sans", "active", { size: "sm", weight: "bold" }),
|
...text(layer, "sans", { size: "sm", weight: "bold" }),
|
||||||
margin: { top: -3 },
|
margin: { top: -3 },
|
||||||
},
|
},
|
||||||
callerMessage: {
|
callerMessage: {
|
||||||
...text(theme, "sans", "secondary", { size: "xs" }),
|
...text(layer, "sans", "variant", { size: "xs" }),
|
||||||
margin: { top: -3 },
|
margin: { top: -3 },
|
||||||
},
|
},
|
||||||
worktreeRoots: {
|
worktreeRoots: {
|
||||||
...text(theme, "sans", "secondary", { size: "xs", weight: "bold" }),
|
...text(layer, "sans", "variant", { size: "xs", weight: "bold" }),
|
||||||
margin: { top: -3 },
|
margin: { top: -3 },
|
||||||
},
|
},
|
||||||
buttonWidth: 96,
|
buttonWidth: 96,
|
||||||
acceptButton: {
|
acceptButton: {
|
||||||
background: backgroundColor(theme, "ok", "active"),
|
background: background(layer, "accent"),
|
||||||
border: { left: true, bottom: true, width: 1, color: borderColor(theme, "primary") },
|
border: border(layer, { left: true, bottom: true }),
|
||||||
...text(theme, "sans", "ok", { size: "xs", weight: "extra_bold" })
|
...text(layer, "sans", "positive", { size: "xs", weight: "extra_bold" })
|
||||||
},
|
},
|
||||||
declineButton: {
|
declineButton: {
|
||||||
border: { left: true, width: 1, color: borderColor(theme, "primary") },
|
border: border(layer, { left: true }),
|
||||||
...text(theme, "sans", "error", { size: "xs", weight: "extra_bold" })
|
...text(layer, "sans", "negative", { size: "xs", weight: "extra_bold" })
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
import Theme from "../themes/common/theme";
|
import { ColorScheme } from "../themes/common/colorScheme";
|
||||||
import {
|
import { background, border, text } from "./components";
|
||||||
backgroundColor,
|
|
||||||
border,
|
|
||||||
player,
|
|
||||||
modalShadow,
|
|
||||||
text,
|
|
||||||
} from "./components";
|
|
||||||
|
|
||||||
export default function picker(theme: Theme) {
|
export default function picker(colorScheme: ColorScheme) {
|
||||||
|
let layer = colorScheme.lowest;
|
||||||
return {
|
return {
|
||||||
background: backgroundColor(theme, 300),
|
background: background(layer),
|
||||||
cornerRadius: 8,
|
border: border(layer),
|
||||||
padding: 8,
|
shadow: colorScheme.modalShadow,
|
||||||
|
cornerRadius: 12,
|
||||||
|
padding: {
|
||||||
|
bottom: 4,
|
||||||
|
},
|
||||||
item: {
|
item: {
|
||||||
padding: {
|
padding: {
|
||||||
bottom: 4,
|
bottom: 4,
|
||||||
|
@ -19,41 +18,48 @@ export default function picker(theme: Theme) {
|
||||||
right: 12,
|
right: 12,
|
||||||
top: 4,
|
top: 4,
|
||||||
},
|
},
|
||||||
|
margin: {
|
||||||
|
top: 1,
|
||||||
|
left: 4,
|
||||||
|
right: 4,
|
||||||
|
},
|
||||||
cornerRadius: 8,
|
cornerRadius: 8,
|
||||||
text: text(theme, "sans", "secondary"),
|
text: text(layer, "sans", "variant"),
|
||||||
highlightText: text(theme, "sans", "feature", { weight: "bold" }),
|
highlightText: text(layer, "sans", "accent", { weight: "bold" }),
|
||||||
active: {
|
active: {
|
||||||
background: backgroundColor(theme, 300, "active"),
|
background: background(layer, "base", "active"),
|
||||||
text: text(theme, "sans", "active"),
|
text: text(layer, "sans", "base", "active"),
|
||||||
|
highlightText: text(layer, "sans", "accent", {
|
||||||
|
weight: "bold",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
hover: {
|
hover: {
|
||||||
background: backgroundColor(theme, 300, "hovered"),
|
background: background(layer, "hovered"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
border: border(theme, "primary"),
|
|
||||||
empty: {
|
empty: {
|
||||||
text: text(theme, "sans", "muted"),
|
text: text(layer, "sans", "variant"),
|
||||||
padding: {
|
padding: {
|
||||||
bottom: 4,
|
bottom: 8,
|
||||||
left: 12,
|
left: 16,
|
||||||
right: 12,
|
right: 16,
|
||||||
top: 8,
|
top: 8,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
inputEditor: {
|
inputEditor: {
|
||||||
background: backgroundColor(theme, 500),
|
placeholderText: text(layer, "sans", "on", "disabled"),
|
||||||
cornerRadius: 8,
|
selection: colorScheme.players[0],
|
||||||
placeholderText: text(theme, "sans", "placeholder"),
|
text: text(layer, "mono", "on"),
|
||||||
selection: player(theme, 1).selection,
|
border: border(layer, { bottom: true }),
|
||||||
text: text(theme, "mono", "primary"),
|
|
||||||
border: border(theme, "secondary"),
|
|
||||||
padding: {
|
padding: {
|
||||||
bottom: 7,
|
bottom: 8,
|
||||||
left: 16,
|
left: 16,
|
||||||
right: 16,
|
right: 16,
|
||||||
top: 7,
|
top: 8,
|
||||||
|
},
|
||||||
|
margin: {
|
||||||
|
bottom: 4,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
shadow: modalShadow(theme),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import Theme from "../themes/common/theme";
|
import { ColorScheme } from "../themes/common/colorScheme";
|
||||||
import { backgroundColor, text } from "./components";
|
import { background, text } from "./components";
|
||||||
|
|
||||||
export default function projectDiagnostics(theme: Theme) {
|
export default function projectDiagnostics(colorScheme: ColorScheme) {
|
||||||
|
let layer = colorScheme.highest;
|
||||||
return {
|
return {
|
||||||
background: backgroundColor(theme, 500),
|
background: background(layer),
|
||||||
tabIconSpacing: 4,
|
tabIconSpacing: 4,
|
||||||
tabIconWidth: 13,
|
tabIconWidth: 13,
|
||||||
tabSummarySpacing: 10,
|
tabSummarySpacing: 10,
|
||||||
emptyMessage: text(theme, "sans", "secondary", { size: "md" }),
|
emptyMessage: text(layer, "sans", "variant", { size: "md" }),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,36 +1,36 @@
|
||||||
import Theme from "../themes/common/theme";
|
import { ColorScheme } from "../themes/common/colorScheme";
|
||||||
import { panel } from "./app";
|
import { background, foreground, text } from "./components";
|
||||||
import { backgroundColor, iconColor, player, text } from "./components";
|
|
||||||
|
|
||||||
export default function projectPanel(theme: Theme) {
|
export default function projectPanel(colorScheme: ColorScheme) {
|
||||||
|
let layer = colorScheme.middle;
|
||||||
return {
|
return {
|
||||||
...panel,
|
background: background(layer),
|
||||||
padding: { left: 12, right: 12, top: 6, bottom: 6 },
|
padding: { left: 12, right: 12, top: 6, bottom: 6 },
|
||||||
indentWidth: 8,
|
indentWidth: 8,
|
||||||
entry: {
|
entry: {
|
||||||
height: 24,
|
height: 24,
|
||||||
iconColor: iconColor(theme, "muted"),
|
iconColor: foreground(layer, "variant"),
|
||||||
iconSize: 8,
|
iconSize: 8,
|
||||||
iconSpacing: 8,
|
iconSpacing: 8,
|
||||||
text: text(theme, "mono", "secondary", { size: "sm" }),
|
text: text(layer, "mono", "variant", { size: "sm" }),
|
||||||
hover: {
|
hover: {
|
||||||
background: backgroundColor(theme, 300, "hovered"),
|
background: background(layer, "variant", "hovered"),
|
||||||
},
|
},
|
||||||
active: {
|
active: {
|
||||||
background: backgroundColor(theme, 300, "active"),
|
background: background(layer, "active"),
|
||||||
text: text(theme, "mono", "active", { size: "sm" }),
|
text: text(layer, "mono", "active", { size: "sm" }),
|
||||||
},
|
},
|
||||||
activeHover: {
|
activeHover: {
|
||||||
background: backgroundColor(theme, 300, "active"),
|
background: background(layer, "active"),
|
||||||
text: text(theme, "mono", "active", { size: "sm" }),
|
text: text(layer, "mono", "active", { size: "sm" }),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
cutEntryFade: 0.4,
|
cutEntryFade: 0.4,
|
||||||
ignoredEntryFade: 0.6,
|
ignoredEntryFade: 0.6,
|
||||||
filenameEditor: {
|
filenameEditor: {
|
||||||
background: backgroundColor(theme, "on300"),
|
background: background(layer, "on"),
|
||||||
text: text(theme, "mono", "active", { size: "sm" }),
|
text: text(layer, "mono", "on", { size: "sm" }),
|
||||||
selection: player(theme, 1).selection,
|
selection: colorScheme.players[0],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
import Theme from "../themes/common/theme";
|
import { ColorScheme } from "../themes/common/colorScheme";
|
||||||
import { backgroundColor, borderColor, text } from "./components";
|
import { background, border, text } from "./components";
|
||||||
|
|
||||||
|
export default function projectSharedNotification(colorScheme: ColorScheme): Object {
|
||||||
|
let layer = colorScheme.middle;
|
||||||
|
|
||||||
export default function projectSharedNotification(theme: Theme): Object {
|
|
||||||
const avatarSize = 48;
|
const avatarSize = 48;
|
||||||
return {
|
return {
|
||||||
windowHeight: 74,
|
windowHeight: 74,
|
||||||
windowWidth: 380,
|
windowWidth: 380,
|
||||||
background: backgroundColor(theme, 300),
|
background: background(layer),
|
||||||
ownerContainer: {
|
ownerContainer: {
|
||||||
padding: 12,
|
padding: 12,
|
||||||
},
|
},
|
||||||
|
@ -19,26 +21,26 @@ export default function projectSharedNotification(theme: Theme): Object {
|
||||||
margin: { left: 10 },
|
margin: { left: 10 },
|
||||||
},
|
},
|
||||||
ownerUsername: {
|
ownerUsername: {
|
||||||
...text(theme, "sans", "active", { size: "sm", weight: "bold" }),
|
...text(layer, "sans", { size: "sm", weight: "bold" }),
|
||||||
margin: { top: -3 },
|
margin: { top: -3 },
|
||||||
},
|
},
|
||||||
message: {
|
message: {
|
||||||
...text(theme, "sans", "secondary", { size: "xs" }),
|
...text(layer, "sans", "variant", { size: "xs" }),
|
||||||
margin: { top: -3 },
|
margin: { top: -3 },
|
||||||
},
|
},
|
||||||
worktreeRoots: {
|
worktreeRoots: {
|
||||||
...text(theme, "sans", "secondary", { size: "xs", weight: "bold" }),
|
...text(layer, "sans", "variant", { size: "xs", weight: "bold" }),
|
||||||
margin: { top: -3 },
|
margin: { top: -3 },
|
||||||
},
|
},
|
||||||
buttonWidth: 96,
|
buttonWidth: 96,
|
||||||
openButton: {
|
openButton: {
|
||||||
background: backgroundColor(theme, "info", "active"),
|
background: background(layer, "accent"),
|
||||||
border: { left: true, bottom: true, width: 1, color: borderColor(theme, "primary") },
|
border: border(layer, { left: true, bottom: true, }),
|
||||||
...text(theme, "sans", "info", { size: "xs", weight: "extra_bold" })
|
...text(layer, "sans", "accent", { size: "xs", weight: "extra_bold" })
|
||||||
},
|
},
|
||||||
dismissButton: {
|
dismissButton: {
|
||||||
border: { left: true, width: 1, color: borderColor(theme, "primary") },
|
border: border(layer, { left: true }),
|
||||||
...text(theme, "sans", "secondary", { size: "xs", weight: "extra_bold" })
|
...text(layer, "sans", "variant", { size: "xs", weight: "extra_bold" })
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
import Theme from "../themes/common/theme";
|
import { ColorScheme } from "../themes/common/colorScheme";
|
||||||
import { backgroundColor, border, player, text } from "./components";
|
import { background, border, text } from "./components";
|
||||||
|
|
||||||
|
export default function search(colorScheme: ColorScheme) {
|
||||||
|
let layer = colorScheme.highest;
|
||||||
|
|
||||||
export default function search(theme: Theme) {
|
|
||||||
// Search input
|
// Search input
|
||||||
const editor = {
|
const editor = {
|
||||||
background: backgroundColor(theme, 500),
|
background: background(layer),
|
||||||
cornerRadius: 8,
|
cornerRadius: 8,
|
||||||
minWidth: 200,
|
minWidth: 200,
|
||||||
maxWidth: 500,
|
maxWidth: 500,
|
||||||
placeholderText: text(theme, "mono", "placeholder"),
|
placeholderText: text(layer, "mono", "disabled"),
|
||||||
selection: player(theme, 1).selection,
|
selection: colorScheme.players[0],
|
||||||
text: text(theme, "mono", "active"),
|
text: text(layer, "mono", "default"),
|
||||||
border: border(theme, "secondary"),
|
border: border(layer),
|
||||||
margin: {
|
margin: {
|
||||||
right: 12,
|
right: 12,
|
||||||
},
|
},
|
||||||
|
@ -24,14 +26,14 @@ export default function search(theme: Theme) {
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
matchBackground: theme.editor.highlight.match,
|
matchBackground: background(layer), // theme.editor.highlight.match,
|
||||||
tabIconSpacing: 8,
|
tabIconSpacing: 8,
|
||||||
tabIconWidth: 14,
|
tabIconWidth: 14,
|
||||||
optionButton: {
|
optionButton: {
|
||||||
...text(theme, "mono", "secondary"),
|
...text(layer, "mono", "on"),
|
||||||
background: backgroundColor(theme, "on500"),
|
background: background(layer, "on"),
|
||||||
cornerRadius: 6,
|
cornerRadius: 6,
|
||||||
border: border(theme, "secondary"),
|
border: border(layer, "on"),
|
||||||
margin: {
|
margin: {
|
||||||
right: 4,
|
right: 4,
|
||||||
},
|
},
|
||||||
|
@ -42,28 +44,28 @@ export default function search(theme: Theme) {
|
||||||
top: 2,
|
top: 2,
|
||||||
},
|
},
|
||||||
active: {
|
active: {
|
||||||
...text(theme, "mono", "active"),
|
...text(layer, "mono", "on", "inverted"),
|
||||||
background: backgroundColor(theme, "on500", "active"),
|
background: background(layer, "on", "inverted"),
|
||||||
border: border(theme, "muted"),
|
border: border(layer, "on", "inverted"),
|
||||||
},
|
},
|
||||||
clicked: {
|
clicked: {
|
||||||
...text(theme, "mono", "active"),
|
...text(layer, "mono", "on", "pressed"),
|
||||||
background: backgroundColor(theme, "on300", "active"),
|
background: background(layer, "on", "pressed"),
|
||||||
border: border(theme, "secondary"),
|
border: border(layer, "on", "pressed"),
|
||||||
},
|
},
|
||||||
hover: {
|
hover: {
|
||||||
...text(theme, "mono", "active"),
|
...text(layer, "mono", "on", "hovered"),
|
||||||
background: backgroundColor(theme, "on500", "hovered"),
|
background: background(layer, "on", "hovered"),
|
||||||
border: border(theme, "muted"),
|
border: border(layer, "on", "hovered"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
editor,
|
editor,
|
||||||
invalidEditor: {
|
invalidEditor: {
|
||||||
...editor,
|
...editor,
|
||||||
border: border(theme, "error"),
|
border: border(layer, "negative"),
|
||||||
},
|
},
|
||||||
matchIndex: {
|
matchIndex: {
|
||||||
...text(theme, "mono", "muted"),
|
...text(layer, "mono", "variant"),
|
||||||
padding: 6,
|
padding: 6,
|
||||||
},
|
},
|
||||||
optionButtonGroup: {
|
optionButtonGroup: {
|
||||||
|
@ -73,7 +75,7 @@ export default function search(theme: Theme) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
resultsStatus: {
|
resultsStatus: {
|
||||||
...text(theme, "mono", "primary"),
|
...text(layer, "mono", "on"),
|
||||||
size: 18,
|
size: 18,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import Theme from "../themes/common/theme";
|
import { ColorScheme } from "../themes/common/colorScheme";
|
||||||
import { backgroundColor, border, iconColor, text } from "./components";
|
import { background, border, foreground, text } from "./components";
|
||||||
import { workspaceBackground } from "./workspace";
|
|
||||||
|
export default function statusBar(colorScheme: ColorScheme) {
|
||||||
|
let layer = colorScheme.lowest;
|
||||||
|
|
||||||
export default function statusBar(theme: Theme) {
|
|
||||||
const statusContainer = {
|
const statusContainer = {
|
||||||
cornerRadius: 6,
|
cornerRadius: 6,
|
||||||
padding: { top: 3, bottom: 3, left: 6, right: 6 },
|
padding: { top: 3, bottom: 3, left: 6, right: 6 },
|
||||||
|
@ -22,70 +23,70 @@ export default function statusBar(theme: Theme) {
|
||||||
left: 6,
|
left: 6,
|
||||||
right: 6,
|
right: 6,
|
||||||
},
|
},
|
||||||
border: border(theme, "primary", { top: true, overlay: true }),
|
border: border(layer, { top: true, overlay: true }),
|
||||||
cursorPosition: text(theme, "sans", "secondary"),
|
cursorPosition: text(layer, "sans", "variant"),
|
||||||
autoUpdateProgressMessage: text(theme, "sans", "secondary"),
|
autoUpdateProgressMessage: text(layer, "sans", "variant"),
|
||||||
autoUpdateDoneMessage: text(theme, "sans", "secondary"),
|
autoUpdateDoneMessage: text(layer, "sans", "variant"),
|
||||||
lspStatus: {
|
lspStatus: {
|
||||||
...diagnosticStatusContainer,
|
...diagnosticStatusContainer,
|
||||||
iconSpacing: 4,
|
iconSpacing: 4,
|
||||||
iconWidth: 14,
|
iconWidth: 14,
|
||||||
height: 18,
|
height: 18,
|
||||||
message: text(theme, "sans", "secondary"),
|
message: text(layer, "sans"),
|
||||||
iconColor: iconColor(theme, "muted"),
|
iconColor: foreground(layer),
|
||||||
hover: {
|
hover: {
|
||||||
message: text(theme, "sans", "primary"),
|
message: text(layer, "sans"),
|
||||||
iconColor: iconColor(theme, "primary"),
|
iconColor: foreground(layer),
|
||||||
background: backgroundColor(theme, 300, "hovered"),
|
background: background(layer),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
diagnosticMessage: {
|
diagnosticMessage: {
|
||||||
...text(theme, "sans", "secondary"),
|
...text(layer, "sans"),
|
||||||
hover: text(theme, "sans", "active"),
|
hover: text(layer, "sans", "hovered"),
|
||||||
},
|
},
|
||||||
feedback: {
|
feedback: {
|
||||||
...text(theme, "sans", "secondary"),
|
...text(layer, "sans", "variant"),
|
||||||
hover: text(theme, "sans", "active"),
|
hover: text(layer, "sans", "hovered"),
|
||||||
},
|
},
|
||||||
diagnosticSummary: {
|
diagnosticSummary: {
|
||||||
height: 16,
|
height: 20,
|
||||||
iconWidth: 16,
|
iconWidth: 16,
|
||||||
iconSpacing: 2,
|
iconSpacing: 2,
|
||||||
summarySpacing: 6,
|
summarySpacing: 6,
|
||||||
text: text(theme, "sans", "primary", { size: "sm" }),
|
text: text(layer, "sans", { size: "sm" }),
|
||||||
iconColorOk: iconColor(theme, "muted"),
|
iconColorOk: foreground(layer, "variant"),
|
||||||
iconColorWarning: iconColor(theme, "warning"),
|
iconColorWarning: foreground(layer, "warning"),
|
||||||
iconColorError: iconColor(theme, "error"),
|
iconColorError: foreground(layer, "negative"),
|
||||||
containerOk: {
|
containerOk: {
|
||||||
cornerRadius: 6,
|
cornerRadius: 6,
|
||||||
padding: { top: 3, bottom: 3, left: 7, right: 7 },
|
padding: { top: 3, bottom: 3, left: 7, right: 7 },
|
||||||
},
|
},
|
||||||
containerWarning: {
|
containerWarning: {
|
||||||
...diagnosticStatusContainer,
|
...diagnosticStatusContainer,
|
||||||
background: backgroundColor(theme, "warning"),
|
background: background(layer, "warning"),
|
||||||
border: border(theme, "warning"),
|
border: border(layer, "warning"),
|
||||||
},
|
},
|
||||||
containerError: {
|
containerError: {
|
||||||
...diagnosticStatusContainer,
|
...diagnosticStatusContainer,
|
||||||
background: backgroundColor(theme, "error"),
|
background: background(layer, "negative"),
|
||||||
border: border(theme, "error"),
|
border: border(layer, "negative"),
|
||||||
},
|
},
|
||||||
hover: {
|
hover: {
|
||||||
iconColorOk: iconColor(theme, "active"),
|
iconColorOk: foreground(layer, "on"),
|
||||||
containerOk: {
|
containerOk: {
|
||||||
cornerRadius: 6,
|
cornerRadius: 6,
|
||||||
padding: { top: 3, bottom: 3, left: 7, right: 7 },
|
padding: { top: 3, bottom: 3, left: 7, right: 7 },
|
||||||
background: backgroundColor(theme, 300, "hovered"),
|
background: background(layer, "on", "hovered"),
|
||||||
},
|
},
|
||||||
containerWarning: {
|
containerWarning: {
|
||||||
...diagnosticStatusContainer,
|
...diagnosticStatusContainer,
|
||||||
background: backgroundColor(theme, "warning", "hovered"),
|
background: background(layer, "warning", "hovered"),
|
||||||
border: border(theme, "warning"),
|
border: border(layer, "warning", "hovered"),
|
||||||
},
|
},
|
||||||
containerError: {
|
containerError: {
|
||||||
...diagnosticStatusContainer,
|
...diagnosticStatusContainer,
|
||||||
background: backgroundColor(theme, "error", "hovered"),
|
background: background(layer, "negative", "hovered"),
|
||||||
border: border(theme, "error"),
|
border: border(layer, "negative", "hovered"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -95,22 +96,22 @@ export default function statusBar(theme: Theme) {
|
||||||
item: {
|
item: {
|
||||||
...statusContainer,
|
...statusContainer,
|
||||||
iconSize: 16,
|
iconSize: 16,
|
||||||
iconColor: iconColor(theme, "muted"),
|
iconColor: foreground(layer, "variant"),
|
||||||
hover: {
|
hover: {
|
||||||
iconColor: iconColor(theme, "active"),
|
iconColor: foreground(layer, "hovered"),
|
||||||
background: backgroundColor(theme, 300, "hovered"),
|
background: background(layer, "variant"),
|
||||||
},
|
},
|
||||||
active: {
|
active: {
|
||||||
iconColor: iconColor(theme, "active"),
|
iconColor: foreground(layer, "active"),
|
||||||
background: backgroundColor(theme, 300, "active"),
|
background: background(layer, "active"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
badge: {
|
badge: {
|
||||||
cornerRadius: 3,
|
cornerRadius: 3,
|
||||||
padding: 2,
|
padding: 2,
|
||||||
margin: { bottom: -1, right: -1 },
|
margin: { bottom: -1, right: -1 },
|
||||||
border: { width: 1, color: workspaceBackground(theme) },
|
border: border(layer),
|
||||||
background: iconColor(theme, "feature"),
|
background: background(layer, "accent"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,76 +1,84 @@
|
||||||
import Theme from "../themes/common/theme";
|
import { ColorScheme } from "../themes/common/colorScheme";
|
||||||
import { withOpacity } from "../utils/color";
|
import { withOpacity } from "../utils/color";
|
||||||
import { iconColor, text, border, backgroundColor, draggedShadow } from "./components";
|
import { text, border, background, foreground } from "./components";
|
||||||
|
|
||||||
export default function tabBar(theme: Theme) {
|
export default function tabBar(colorScheme: ColorScheme) {
|
||||||
const height = 32;
|
const height = 32;
|
||||||
|
|
||||||
|
let activeLayer = colorScheme.highest;
|
||||||
|
let layer = colorScheme.middle;
|
||||||
|
|
||||||
const tab = {
|
const tab = {
|
||||||
height,
|
height,
|
||||||
background: backgroundColor(theme, 300),
|
text: text(layer, "sans", "variant", { size: "sm" }),
|
||||||
border: border(theme, "primary", {
|
background: background(layer),
|
||||||
left: true,
|
border: border(layer, {
|
||||||
|
right: true,
|
||||||
bottom: true,
|
bottom: true,
|
||||||
overlay: true,
|
overlay: true,
|
||||||
}),
|
}),
|
||||||
iconClose: iconColor(theme, "muted"),
|
|
||||||
iconCloseActive: iconColor(theme, "active"),
|
|
||||||
iconConflict: iconColor(theme, "warning"),
|
|
||||||
iconDirty: iconColor(theme, "info"),
|
|
||||||
iconWidth: 8,
|
|
||||||
spacing: 8,
|
|
||||||
text: text(theme, "sans", "secondary", { size: "sm" }),
|
|
||||||
padding: {
|
padding: {
|
||||||
left: 8,
|
left: 8,
|
||||||
right: 8,
|
right: 12,
|
||||||
},
|
},
|
||||||
|
spacing: 8,
|
||||||
|
|
||||||
|
// Close icons
|
||||||
|
iconWidth: 8,
|
||||||
|
iconClose: foreground(layer, "variant"),
|
||||||
|
iconCloseActive: foreground(layer),
|
||||||
|
|
||||||
|
// Indicators
|
||||||
|
iconConflict: foreground(layer, "warning"),
|
||||||
|
iconDirty: foreground(layer, "accent"),
|
||||||
|
|
||||||
|
// When two tabs of the same name are open, a label appears next to them
|
||||||
description: {
|
description: {
|
||||||
margin: { left: 6, top: 1 },
|
margin: { left: 8 },
|
||||||
...text(theme, "sans", "muted", { size: "2xs" })
|
...text(layer, "sans", "disabled", { size: "2xs" }),
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const activePaneActiveTab = {
|
const activePaneActiveTab = {
|
||||||
...tab,
|
...tab,
|
||||||
background: backgroundColor(theme, 500),
|
background: background(activeLayer),
|
||||||
text: text(theme, "sans", "active", { size: "sm" }),
|
text: text(activeLayer, "sans", "active", { size: "sm" }),
|
||||||
border: {
|
border: {
|
||||||
...tab.border,
|
...tab.border,
|
||||||
bottom: false
|
bottom: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const inactivePaneInactiveTab = {
|
const inactivePaneInactiveTab = {
|
||||||
...tab,
|
...tab,
|
||||||
background: backgroundColor(theme, 300),
|
background: background(layer),
|
||||||
text: text(theme, "sans", "muted", { size: "sm" }),
|
text: text(layer, "sans", "variant", { size: "sm" }),
|
||||||
};
|
};
|
||||||
|
|
||||||
const inactivePaneActiveTab = {
|
const inactivePaneActiveTab = {
|
||||||
...tab,
|
...tab,
|
||||||
background: backgroundColor(theme, 500),
|
background: background(activeLayer),
|
||||||
text: text(theme, "sans", "secondary", { size: "sm" }),
|
text: text(layer, "sans", "variant", { size: "sm" }),
|
||||||
border: {
|
border: {
|
||||||
...tab.border,
|
...tab.border,
|
||||||
bottom: false
|
bottom: false,
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
const draggedTab = {
|
const draggedTab = {
|
||||||
...activePaneActiveTab,
|
...activePaneActiveTab,
|
||||||
background: withOpacity(tab.background, 0.8),
|
background: withOpacity(tab.background, 0.95),
|
||||||
border: undefined as any, // Remove border
|
border: undefined as any,
|
||||||
shadow: draggedShadow(theme),
|
shadow: colorScheme.popoverShadow,
|
||||||
}
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
height,
|
height,
|
||||||
background: backgroundColor(theme, 300),
|
background: background(layer),
|
||||||
dropTargetOverlayColor: withOpacity(theme.textColor.muted, 0.6),
|
dropTargetOverlayColor: withOpacity(
|
||||||
border: border(theme, "primary", {
|
foreground(layer),
|
||||||
bottom: true,
|
0.6
|
||||||
overlay: true,
|
),
|
||||||
}),
|
|
||||||
activePane: {
|
activePane: {
|
||||||
activeTab: activePaneActiveTab,
|
activeTab: activePaneActiveTab,
|
||||||
inactiveTab: tab,
|
inactiveTab: tab,
|
||||||
|
@ -81,11 +89,11 @@ export default function tabBar(theme: Theme) {
|
||||||
},
|
},
|
||||||
draggedTab,
|
draggedTab,
|
||||||
paneButton: {
|
paneButton: {
|
||||||
color: iconColor(theme, "secondary"),
|
color: foreground(layer, "variant"),
|
||||||
iconWidth: 12,
|
iconWidth: 12,
|
||||||
buttonWidth: activePaneActiveTab.height,
|
buttonWidth: activePaneActiveTab.height,
|
||||||
hover: {
|
hover: {
|
||||||
color: iconColor(theme, "active"),
|
color: foreground(layer, "hovered"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
paneButtonContainer: {
|
paneButtonContainer: {
|
||||||
|
@ -93,7 +101,7 @@ export default function tabBar(theme: Theme) {
|
||||||
border: {
|
border: {
|
||||||
...tab.border,
|
...tab.border,
|
||||||
right: false,
|
right: false,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,65 +1,52 @@
|
||||||
import Theme from "../themes/common/theme";
|
import { ColorScheme } from "../themes/common/colorScheme";
|
||||||
import { border, modalShadow, player } from "./components";
|
|
||||||
|
|
||||||
export default function terminal(theme: Theme) {
|
export default function terminal(colorScheme: ColorScheme) {
|
||||||
/**
|
/**
|
||||||
* Colors are controlled per-cell in the terminal grid.
|
* Colors are controlled per-cell in the terminal grid.
|
||||||
* Cells can be set to any of these more 'theme-capable' colors
|
* Cells can be set to any of these more 'theme-capable' colors
|
||||||
* or can be set directly with RGB values.
|
* or can be set directly with RGB values.
|
||||||
* Here are the common interpretations of these names:
|
* Here are the common interpretations of these names:
|
||||||
* https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
|
* https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
|
||||||
*/
|
*/
|
||||||
let colors = {
|
return {
|
||||||
black: theme.ramps.neutral(0).hex(),
|
black: colorScheme.ramps.neutral(0).hex(),
|
||||||
red: theme.ramps.red(0.5).hex(),
|
red: colorScheme.ramps.red(0.5).hex(),
|
||||||
green: theme.ramps.green(0.5).hex(),
|
green: colorScheme.ramps.green(0.5).hex(),
|
||||||
yellow: theme.ramps.yellow(0.5).hex(),
|
yellow: colorScheme.ramps.yellow(0.5).hex(),
|
||||||
blue: theme.ramps.blue(0.5).hex(),
|
blue: colorScheme.ramps.blue(0.5).hex(),
|
||||||
magenta: theme.ramps.magenta(0.5).hex(),
|
magenta: colorScheme.ramps.magenta(0.5).hex(),
|
||||||
cyan: theme.ramps.cyan(0.5).hex(),
|
cyan: colorScheme.ramps.cyan(0.5).hex(),
|
||||||
white: theme.ramps.neutral(7).hex(),
|
white: colorScheme.ramps.neutral(1).hex(),
|
||||||
brightBlack: theme.ramps.neutral(4).hex(),
|
brightBlack: colorScheme.ramps.neutral(0.4).hex(),
|
||||||
brightRed: theme.ramps.red(0.25).hex(),
|
brightRed: colorScheme.ramps.red(0.25).hex(),
|
||||||
brightGreen: theme.ramps.green(0.25).hex(),
|
brightGreen: colorScheme.ramps.green(0.25).hex(),
|
||||||
brightYellow: theme.ramps.yellow(0.25).hex(),
|
brightYellow: colorScheme.ramps.yellow(0.25).hex(),
|
||||||
brightBlue: theme.ramps.blue(0.25).hex(),
|
brightBlue: colorScheme.ramps.blue(0.25).hex(),
|
||||||
brightMagenta: theme.ramps.magenta(0.25).hex(),
|
brightMagenta: colorScheme.ramps.magenta(0.25).hex(),
|
||||||
brightCyan: theme.ramps.cyan(0.25).hex(),
|
brightCyan: colorScheme.ramps.cyan(0.25).hex(),
|
||||||
brightWhite: theme.ramps.neutral(7).hex(),
|
brightWhite: colorScheme.ramps.neutral(1).hex(),
|
||||||
/**
|
/**
|
||||||
* Default color for characters
|
* Default color for characters
|
||||||
*/
|
*/
|
||||||
foreground: theme.ramps.neutral(7).hex(),
|
foreground: colorScheme.ramps.neutral(1).hex(),
|
||||||
/**
|
/**
|
||||||
* Default color for the rectangle background of a cell
|
* Default color for the rectangle background of a cell
|
||||||
*/
|
*/
|
||||||
background: theme.ramps.neutral(0).hex(),
|
background: colorScheme.ramps.neutral(0).hex(),
|
||||||
modalBackground: theme.ramps.neutral(1).hex(),
|
modalBackground: colorScheme.ramps.neutral(0.1).hex(),
|
||||||
/**
|
/**
|
||||||
* Default color for the cursor
|
* Default color for the cursor
|
||||||
*/
|
*/
|
||||||
cursor: player(theme, 1).selection.cursor,
|
cursor: colorScheme.players[0].cursor,
|
||||||
dimBlack: theme.ramps.neutral(7).hex(),
|
dimBlack: colorScheme.ramps.neutral(1).hex(),
|
||||||
dimRed: theme.ramps.red(0.75).hex(),
|
dimRed: colorScheme.ramps.red(0.75).hex(),
|
||||||
dimGreen: theme.ramps.green(0.75).hex(),
|
dimGreen: colorScheme.ramps.green(0.75).hex(),
|
||||||
dimYellow: theme.ramps.yellow(0.75).hex(),
|
dimYellow: colorScheme.ramps.yellow(0.75).hex(),
|
||||||
dimBlue: theme.ramps.blue(0.75).hex(),
|
dimBlue: colorScheme.ramps.blue(0.75).hex(),
|
||||||
dimMagenta: theme.ramps.magenta(0.75).hex(),
|
dimMagenta: colorScheme.ramps.magenta(0.75).hex(),
|
||||||
dimCyan: theme.ramps.cyan(0.75).hex(),
|
dimCyan: colorScheme.ramps.cyan(0.75).hex(),
|
||||||
dimWhite: theme.ramps.neutral(5).hex(),
|
dimWhite: colorScheme.ramps.neutral(0.6).hex(),
|
||||||
brightForeground: theme.ramps.neutral(7).hex(),
|
brightForeground: colorScheme.ramps.neutral(1).hex(),
|
||||||
dimForeground: theme.ramps.neutral(0).hex(),
|
dimForeground: colorScheme.ramps.neutral(0).hex(),
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
colors,
|
|
||||||
modalContainer: {
|
|
||||||
background: colors.modalBackground,
|
|
||||||
cornerRadius: 8,
|
|
||||||
padding: 8,
|
|
||||||
margin: 25,
|
|
||||||
border: border(theme, "primary"),
|
|
||||||
shadow: modalShadow(theme),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,22 @@
|
||||||
import Theme from "../themes/common/theme";
|
import { ColorScheme } from "../themes/common/colorScheme";
|
||||||
import { backgroundColor, border, popoverShadow, text } from "./components";
|
import { background, border, text } from "./components";
|
||||||
|
|
||||||
export default function tooltip(theme: Theme) {
|
export default function tooltip(colorScheme: ColorScheme) {
|
||||||
|
let layer = colorScheme.middle;
|
||||||
return {
|
return {
|
||||||
background: backgroundColor(theme, 500),
|
background: background(layer),
|
||||||
border: border(theme, "secondary"),
|
border: border(layer),
|
||||||
padding: { top: 4, bottom: 4, left: 8, right: 8 },
|
padding: { top: 4, bottom: 4, left: 8, right: 8 },
|
||||||
margin: { top: 6, left: 6 },
|
margin: { top: 6, left: 6 },
|
||||||
shadow: popoverShadow(theme),
|
shadow: colorScheme.popoverShadow,
|
||||||
cornerRadius: 6,
|
cornerRadius: 6,
|
||||||
text: text(theme, "sans", "primary", { size: "xs" }),
|
text: text(layer, "sans", { size: "xs" }),
|
||||||
keystroke: {
|
keystroke: {
|
||||||
background: backgroundColor(theme, "on500"),
|
background: background(layer, "on"),
|
||||||
cornerRadius: 4,
|
cornerRadius: 4,
|
||||||
margin: { left: 6 },
|
margin: { left: 6 },
|
||||||
padding: { left: 4, right: 4 },
|
padding: { left: 4, right: 4 },
|
||||||
...text(theme, "mono", "secondary", { size: "xs", weight: "bold" }),
|
...text(layer, "mono", "on", { size: "xs", weight: "bold" }),
|
||||||
},
|
},
|
||||||
maxTextWidth: 200,
|
maxTextWidth: 200,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,29 +1,30 @@
|
||||||
import Theme from "../themes/common/theme";
|
import { ColorScheme } from "../themes/common/colorScheme";
|
||||||
import { iconColor, text } from "./components";
|
import { foreground, text } from "./components";
|
||||||
|
|
||||||
const headerPadding = 8;
|
const headerPadding = 8;
|
||||||
|
|
||||||
export default function updateNotification(theme: Theme): Object {
|
export default function updateNotification(colorScheme: ColorScheme): Object {
|
||||||
|
let layer = colorScheme.middle;
|
||||||
return {
|
return {
|
||||||
message: {
|
message: {
|
||||||
...text(theme, "sans", "primary", { size: "xs" }),
|
...text(layer, "sans", { size: "xs" }),
|
||||||
margin: { left: headerPadding, right: headerPadding },
|
margin: { left: headerPadding, right: headerPadding },
|
||||||
},
|
},
|
||||||
actionMessage: {
|
actionMessage: {
|
||||||
...text(theme, "sans", "secondary", { size: "xs" }),
|
...text(layer, "sans", { size: "xs" }),
|
||||||
margin: { left: headerPadding, top: 6, bottom: 6 },
|
margin: { left: headerPadding, top: 6, bottom: 6 },
|
||||||
hover: {
|
hover: {
|
||||||
color: theme.textColor["active"],
|
color: foreground(layer, "hovered"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
dismissButton: {
|
dismissButton: {
|
||||||
color: iconColor(theme, "secondary"),
|
color: foreground(layer),
|
||||||
iconWidth: 8,
|
iconWidth: 8,
|
||||||
iconHeight: 8,
|
iconHeight: 8,
|
||||||
buttonWidth: 8,
|
buttonWidth: 8,
|
||||||
buttonHeight: 8,
|
buttonHeight: 8,
|
||||||
hover: {
|
hover: {
|
||||||
color: iconColor(theme, "primary"),
|
color: foreground(layer, "hovered"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,64 +1,58 @@
|
||||||
import Theme from "../themes/common/theme";
|
import { ColorScheme } from "../themes/common/colorScheme";
|
||||||
import { withOpacity } from "../utils/color";
|
import { withOpacity } from "../utils/color";
|
||||||
import {
|
import {
|
||||||
backgroundColor,
|
background,
|
||||||
border,
|
border,
|
||||||
iconColor,
|
borderColor,
|
||||||
modalShadow,
|
foreground,
|
||||||
text,
|
text,
|
||||||
} from "./components";
|
} from "./components";
|
||||||
import statusBar from "./statusBar";
|
import statusBar from "./statusBar";
|
||||||
import tabBar from "./tabBar";
|
import tabBar from "./tabBar";
|
||||||
|
|
||||||
export function workspaceBackground(theme: Theme) {
|
export default function workspace(colorScheme: ColorScheme) {
|
||||||
return backgroundColor(theme, 300);
|
const layer = colorScheme.lowest;
|
||||||
}
|
|
||||||
|
|
||||||
export default function workspace(theme: Theme) {
|
|
||||||
const titlebarPadding = 6;
|
const titlebarPadding = 6;
|
||||||
const titlebarButton = {
|
const titlebarButton = {
|
||||||
background: backgroundColor(theme, 100),
|
|
||||||
border: border(theme, "secondary"),
|
|
||||||
cornerRadius: 6,
|
cornerRadius: 6,
|
||||||
margin: {
|
|
||||||
top: 1,
|
|
||||||
},
|
|
||||||
padding: {
|
padding: {
|
||||||
top: 1,
|
top: 1,
|
||||||
bottom: 1,
|
bottom: 1,
|
||||||
left: 7,
|
left: 8,
|
||||||
right: 7,
|
right: 8,
|
||||||
},
|
},
|
||||||
...text(theme, "sans", "secondary", { size: "xs" }),
|
...text(layer, "sans", "variant", { size: "xs" }),
|
||||||
|
background: background(layer, "variant"),
|
||||||
|
border: border(layer),
|
||||||
hover: {
|
hover: {
|
||||||
...text(theme, "sans", "active", { size: "xs" }),
|
...text(layer, "sans", "variant", "hovered", { size: "xs" }),
|
||||||
background: backgroundColor(theme, "on300", "hovered"),
|
background: background(layer, "variant", "hovered"),
|
||||||
border: border(theme, "primary"),
|
border: border(layer, "variant", "hovered"),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const avatarWidth = 18;
|
const avatarWidth = 18;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
background: backgroundColor(theme, 300),
|
background: background(layer),
|
||||||
joiningProjectAvatar: {
|
joiningProjectAvatar: {
|
||||||
cornerRadius: 40,
|
cornerRadius: 40,
|
||||||
width: 80,
|
width: 80,
|
||||||
},
|
},
|
||||||
joiningProjectMessage: {
|
joiningProjectMessage: {
|
||||||
padding: 12,
|
padding: 12,
|
||||||
...text(theme, "sans", "primary", { size: "lg" }),
|
...text(layer, "sans", { size: "lg" }),
|
||||||
},
|
},
|
||||||
externalLocationMessage: {
|
externalLocationMessage: {
|
||||||
background: backgroundColor(theme, "info"),
|
background: background(colorScheme.middle, "accent"),
|
||||||
border: border(theme, "secondary"),
|
border: border(colorScheme.middle, "accent"),
|
||||||
cornerRadius: 6,
|
cornerRadius: 6,
|
||||||
padding: 12,
|
padding: 12,
|
||||||
margin: { bottom: 8, right: 8 },
|
margin: { bottom: 8, right: 8 },
|
||||||
...text(theme, "sans", "secondary", { size: "xs" }),
|
...text(colorScheme.middle, "sans", "accent", { size: "xs" }),
|
||||||
},
|
},
|
||||||
leaderBorderOpacity: 0.7,
|
leaderBorderOpacity: 0.7,
|
||||||
leaderBorderWidth: 2.0,
|
leaderBorderWidth: 2.0,
|
||||||
tabBar: tabBar(theme),
|
tabBar: tabBar(colorScheme),
|
||||||
modal: {
|
modal: {
|
||||||
margin: {
|
margin: {
|
||||||
bottom: 52,
|
bottom: 52,
|
||||||
|
@ -68,28 +62,28 @@ export default function workspace(theme: Theme) {
|
||||||
},
|
},
|
||||||
sidebar: {
|
sidebar: {
|
||||||
initialSize: 240,
|
initialSize: 240,
|
||||||
border: {
|
border: border(layer, { left: true, right: true }),
|
||||||
color: border(theme, "primary").color,
|
|
||||||
width: 1,
|
|
||||||
left: true,
|
|
||||||
right: true,
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
paneDivider: {
|
paneDivider: {
|
||||||
color: border(theme, "secondary").color,
|
color: borderColor(layer),
|
||||||
width: 1,
|
width: 1,
|
||||||
},
|
},
|
||||||
statusBar: statusBar(theme),
|
statusBar: statusBar(colorScheme),
|
||||||
titlebar: {
|
titlebar: {
|
||||||
avatarWidth,
|
avatarWidth,
|
||||||
avatarMargin: 8,
|
avatarMargin: 8,
|
||||||
height: 33,
|
height: 33, // 32px + 1px for overlaid border
|
||||||
background: backgroundColor(theme, 100),
|
background: background(layer),
|
||||||
|
border: border(layer, { bottom: true, overlay: true }),
|
||||||
padding: {
|
padding: {
|
||||||
left: 80,
|
left: 80,
|
||||||
right: titlebarPadding,
|
right: titlebarPadding,
|
||||||
},
|
},
|
||||||
title: text(theme, "sans", "primary"),
|
|
||||||
|
// Project
|
||||||
|
title: text(layer, "sans", "variant"),
|
||||||
|
|
||||||
|
// Collaborators
|
||||||
avatar: {
|
avatar: {
|
||||||
cornerRadius: avatarWidth / 2,
|
cornerRadius: avatarWidth / 2,
|
||||||
border: {
|
border: {
|
||||||
|
@ -108,15 +102,18 @@ export default function workspace(theme: Theme) {
|
||||||
avatarRibbon: {
|
avatarRibbon: {
|
||||||
height: 3,
|
height: 3,
|
||||||
width: 12,
|
width: 12,
|
||||||
// TODO: The background for this ideally should be
|
// TODO: Chore: Make avatarRibbon colors driven by the theme rather than being hard coded.
|
||||||
// set with a token, not hardcoded in rust
|
|
||||||
},
|
},
|
||||||
border: border(theme, "primary", { bottom: true, overlay: true }),
|
|
||||||
|
// Sign in buttom
|
||||||
|
// FlatButton, Variant
|
||||||
signInPrompt: {
|
signInPrompt: {
|
||||||
...titlebarButton
|
...titlebarButton
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Offline Indicator
|
||||||
offlineIcon: {
|
offlineIcon: {
|
||||||
color: iconColor(theme, "secondary"),
|
color: foreground(layer, "variant"),
|
||||||
width: 16,
|
width: 16,
|
||||||
margin: {
|
margin: {
|
||||||
left: titlebarPadding,
|
left: titlebarPadding,
|
||||||
|
@ -125,79 +122,82 @@ export default function workspace(theme: Theme) {
|
||||||
right: 4,
|
right: 4,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Notice that the collaboration server is out of date
|
||||||
outdatedWarning: {
|
outdatedWarning: {
|
||||||
...text(theme, "sans", "warning", { size: "xs" }),
|
...text(layer, "sans", "warning", { size: "xs" }),
|
||||||
background: backgroundColor(theme, "warning"),
|
background: withOpacity(background(layer, "warning"), 0.3),
|
||||||
border: border(theme, "warning"),
|
border: border(layer, "warning"),
|
||||||
margin: {
|
margin: {
|
||||||
left: titlebarPadding,
|
left: titlebarPadding,
|
||||||
},
|
},
|
||||||
padding: {
|
padding: {
|
||||||
left: 6,
|
left: 8,
|
||||||
right: 6,
|
right: 8,
|
||||||
},
|
},
|
||||||
cornerRadius: 6,
|
cornerRadius: 6,
|
||||||
},
|
},
|
||||||
toggleContactsButton: {
|
toggleContactsButton: {
|
||||||
cornerRadius: 6,
|
cornerRadius: 6,
|
||||||
color: iconColor(theme, "secondary"),
|
color: foreground(layer, "variant"),
|
||||||
iconWidth: 8,
|
iconWidth: 8,
|
||||||
buttonWidth: 20,
|
buttonWidth: 20,
|
||||||
active: {
|
active: {
|
||||||
background: backgroundColor(theme, "on300", "active"),
|
background: background(layer, "variant", "active"),
|
||||||
color: iconColor(theme, "active"),
|
color: foreground(layer, "variant", "active"),
|
||||||
},
|
},
|
||||||
hover: {
|
hover: {
|
||||||
background: backgroundColor(theme, "on300", "hovered"),
|
background: background(layer, "variant", "hovered"),
|
||||||
color: iconColor(theme, "active"),
|
color: foreground(layer, "variant", "hovered"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
toggleContactsBadge: {
|
toggleContactsBadge: {
|
||||||
cornerRadius: 3,
|
cornerRadius: 3,
|
||||||
padding: 2,
|
padding: 2,
|
||||||
margin: { top: 3, left: 3 },
|
margin: { top: 3, left: 3 },
|
||||||
border: { width: 1, color: workspaceBackground(theme) },
|
border: border(layer),
|
||||||
background: iconColor(theme, "feature"),
|
background: foreground(layer, "accent"),
|
||||||
},
|
},
|
||||||
shareButton: {
|
shareButton: {
|
||||||
...titlebarButton
|
...titlebarButton
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
toolbar: {
|
toolbar: {
|
||||||
height: 34,
|
height: 34,
|
||||||
background: backgroundColor(theme, 500),
|
background: background(colorScheme.highest),
|
||||||
border: border(theme, "secondary", { bottom: true }),
|
border: border(colorScheme.highest, { bottom: true }),
|
||||||
itemSpacing: 8,
|
itemSpacing: 8,
|
||||||
navButton: {
|
navButton: {
|
||||||
color: iconColor(theme, "primary"),
|
color: foreground(colorScheme.highest, "on"),
|
||||||
iconWidth: 12,
|
iconWidth: 12,
|
||||||
buttonWidth: 24,
|
buttonWidth: 24,
|
||||||
cornerRadius: 6,
|
cornerRadius: 6,
|
||||||
hover: {
|
hover: {
|
||||||
color: iconColor(theme, "active"),
|
color: foreground(colorScheme.highest, "on", "hovered"),
|
||||||
background: backgroundColor(theme, "on500", "hovered"),
|
background: background(colorScheme.highest, "on", "hovered"),
|
||||||
},
|
},
|
||||||
disabled: {
|
disabled: {
|
||||||
color: withOpacity(iconColor(theme, "muted"), 0.6),
|
color: foreground(colorScheme.highest, "on", "disabled"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
padding: { left: 8, right: 8, top: 4, bottom: 4 },
|
padding: { left: 8, right: 8, top: 4, bottom: 4 },
|
||||||
},
|
},
|
||||||
breadcrumbs: {
|
breadcrumbs: {
|
||||||
...text(theme, "mono", "secondary"),
|
...text(layer, "mono", "variant"),
|
||||||
padding: { left: 6 },
|
padding: { left: 6 },
|
||||||
},
|
},
|
||||||
disconnectedOverlay: {
|
disconnectedOverlay: {
|
||||||
...text(theme, "sans", "active"),
|
...text(layer, "sans"),
|
||||||
background: withOpacity(theme.backgroundColor[500].base, 0.8),
|
background: withOpacity(background(layer), 0.8),
|
||||||
},
|
},
|
||||||
notification: {
|
notification: {
|
||||||
margin: { top: 10 },
|
margin: { top: 10 },
|
||||||
background: backgroundColor(theme, 300),
|
background: background(colorScheme.middle),
|
||||||
cornerRadius: 6,
|
cornerRadius: 6,
|
||||||
padding: 12,
|
padding: 12,
|
||||||
border: border(theme, "primary"),
|
border: border(colorScheme.middle),
|
||||||
shadow: modalShadow(theme),
|
shadow: colorScheme.popoverShadow,
|
||||||
},
|
},
|
||||||
notifications: {
|
notifications: {
|
||||||
width: 400,
|
width: 400,
|
||||||
|
@ -206,18 +206,15 @@ export default function workspace(theme: Theme) {
|
||||||
dock: {
|
dock: {
|
||||||
initialSizeRight: 640,
|
initialSizeRight: 640,
|
||||||
initialSizeBottom: 480,
|
initialSizeBottom: 480,
|
||||||
wash_color: withOpacity(theme.backgroundColor[500].base, 0.5),
|
wash_color: withOpacity(background(colorScheme.highest), 0.5),
|
||||||
panel: {
|
panel: {
|
||||||
border: {
|
border: border(colorScheme.highest),
|
||||||
...border(theme, "secondary"),
|
|
||||||
width: 1
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
maximized: {
|
maximized: {
|
||||||
margin: 24,
|
margin: 32,
|
||||||
border: border(theme, "secondary", { "overlay": true }),
|
border: border(colorScheme.highest, { overlay: true }),
|
||||||
shadow: modalShadow(theme),
|
shadow: colorScheme.modalShadow,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
import fs from "fs";
|
|
||||||
import path from "path";
|
|
||||||
import Theme from "./themes/common/theme";
|
|
||||||
|
|
||||||
const themes: Theme[] = [];
|
|
||||||
export default themes;
|
|
||||||
|
|
||||||
const internalThemes: Theme[] = [];
|
|
||||||
export { internalThemes }
|
|
||||||
|
|
||||||
const experimentalThemes: Theme[] = [];
|
|
||||||
export { experimentalThemes }
|
|
||||||
|
|
||||||
|
|
||||||
function fillThemes(themesPath: string, themes: Theme[]) {
|
|
||||||
for (const fileName of fs.readdirSync(themesPath)) {
|
|
||||||
if (fileName == "template.ts") continue;
|
|
||||||
const filePath = path.join(themesPath, fileName);
|
|
||||||
|
|
||||||
if (fs.statSync(filePath).isFile()) {
|
|
||||||
const theme = require(filePath);
|
|
||||||
if (theme.dark) themes.push(theme.dark);
|
|
||||||
if (theme.light) themes.push(theme.light);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fillThemes(path.resolve(`${__dirname}/themes`), themes)
|
|
||||||
fillThemes(path.resolve(`${__dirname}/themes/internal`), internalThemes)
|
|
||||||
fillThemes(path.resolve(`${__dirname}/themes/experiments`), experimentalThemes)
|
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
import chroma from "chroma-js";
|
|
||||||
import { colorRamp, createTheme } from "./common/base16";
|
|
||||||
|
|
||||||
const name = "abruzzo";
|
|
||||||
|
|
||||||
const ramps = {
|
|
||||||
neutral: chroma.scale([
|
|
||||||
"#1b0d05",
|
|
||||||
"#2c1e18",
|
|
||||||
"#654035",
|
|
||||||
"#9d5e4a",
|
|
||||||
"#b37354",
|
|
||||||
"#c1825a",
|
|
||||||
"#dda66e",
|
|
||||||
"#fbf3e2",
|
|
||||||
]),
|
|
||||||
red: colorRamp(chroma("#e594c4")),
|
|
||||||
orange: colorRamp(chroma("#d9e87e")),
|
|
||||||
yellow: colorRamp(chroma("#fd9d83")),
|
|
||||||
green: colorRamp(chroma("#96adf7")),
|
|
||||||
cyan: colorRamp(chroma("#fc798f")),
|
|
||||||
blue: colorRamp(chroma("#BCD0F5")),
|
|
||||||
violet: colorRamp(chroma("#dac5eb")),
|
|
||||||
magenta: colorRamp(chroma("#c1a3ef")),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const dark = createTheme(`${name}`, false, ramps);
|
|
||||||
// export const light = createTheme(`${name}-light`, true, ramps);
|
|
|
@ -1,19 +1,21 @@
|
||||||
import chroma from "chroma-js";
|
import chroma from "chroma-js";
|
||||||
import { colorRamp, createTheme } from "./common/base16";
|
import { colorRamp, createColorScheme } from "./common/ramps";
|
||||||
|
|
||||||
const name = "andromeda";
|
const name = "andromeda";
|
||||||
|
|
||||||
const ramps = {
|
const ramps = {
|
||||||
neutral: chroma.scale([
|
neutral: chroma
|
||||||
"#1E2025",
|
.scale([
|
||||||
"#23262E",
|
"#1E2025",
|
||||||
"#292E38",
|
"#23262E",
|
||||||
"#2E323C",
|
"#292E38",
|
||||||
"#ACA8AE",
|
"#2E323C",
|
||||||
"#CBC9CF",
|
"#ACA8AE",
|
||||||
"#E1DDE4",
|
"#CBC9CF",
|
||||||
"#F7F7F8",
|
"#E1DDE4",
|
||||||
]),
|
"#F7F7F8",
|
||||||
|
])
|
||||||
|
.domain([0, 0.15, 0.25, 0.35, 0.7, 0.8, 0.9, 1]),
|
||||||
red: colorRamp(chroma("#F92672")),
|
red: colorRamp(chroma("#F92672")),
|
||||||
orange: colorRamp(chroma("#F39C12")),
|
orange: colorRamp(chroma("#F39C12")),
|
||||||
yellow: colorRamp(chroma("#FFE66D")),
|
yellow: colorRamp(chroma("#FFE66D")),
|
||||||
|
@ -24,4 +26,4 @@ const ramps = {
|
||||||
magenta: colorRamp(chroma("#C74DED")),
|
magenta: colorRamp(chroma("#C74DED")),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dark = createTheme(`${name}`, false, ramps);
|
export const dark = createColorScheme(`${name}`, false, ramps);
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
import chroma from "chroma-js";
|
|
||||||
import { colorRamp, createTheme } from "./common/base16";
|
|
||||||
|
|
||||||
const name = "brush-tree";
|
|
||||||
|
|
||||||
const ramps = {
|
|
||||||
neutral: chroma.scale([
|
|
||||||
"#485867",
|
|
||||||
"#5A6D7A",
|
|
||||||
"#6D828E",
|
|
||||||
"#8299A1",
|
|
||||||
"#98AFB5",
|
|
||||||
"#B0C5C8",
|
|
||||||
"#C9DBDC",
|
|
||||||
"#E3EFEF",
|
|
||||||
]),
|
|
||||||
red: colorRamp(chroma("#b38686")),
|
|
||||||
orange: colorRamp(chroma("#d8bba2")),
|
|
||||||
yellow: colorRamp(chroma("#aab386")),
|
|
||||||
green: colorRamp(chroma("#87b386")),
|
|
||||||
cyan: colorRamp(chroma("#86b3b3")),
|
|
||||||
blue: colorRamp(chroma("#868cb3")),
|
|
||||||
violet: colorRamp(chroma("#b386b2")),
|
|
||||||
magenta: colorRamp(chroma("#b39f9f")),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const dark = createTheme(`${name}-dark`, false, ramps);
|
|
||||||
export const light = createTheme(`${name}-light`, true, ramps);
|
|
|
@ -1,19 +1,21 @@
|
||||||
import chroma from "chroma-js";
|
import chroma from "chroma-js";
|
||||||
import { colorRamp, createTheme } from "./common/base16";
|
import { colorRamp, createColorScheme } from "./common/ramps";
|
||||||
|
|
||||||
const name = "cave";
|
const name = "cave";
|
||||||
|
|
||||||
const ramps = {
|
export const dark = createColorScheme(`${name}-dark`, false, {
|
||||||
neutral: chroma.scale([
|
neutral: chroma
|
||||||
"#19171c",
|
.scale([
|
||||||
"#26232a",
|
"#19171c",
|
||||||
"#585260",
|
"#26232a",
|
||||||
"#655f6d",
|
"#585260",
|
||||||
"#7e7887",
|
"#655f6d",
|
||||||
"#8b8792",
|
"#7e7887",
|
||||||
"#e2dfe7",
|
"#8b8792",
|
||||||
"#efecf4",
|
"#e2dfe7",
|
||||||
]),
|
"#efecf4",
|
||||||
|
])
|
||||||
|
.domain([0, 0.15, 0.45, 0.6, 0.65, 0.7, 0.85, 1]),
|
||||||
red: colorRamp(chroma("#be4678")),
|
red: colorRamp(chroma("#be4678")),
|
||||||
orange: colorRamp(chroma("#aa573c")),
|
orange: colorRamp(chroma("#aa573c")),
|
||||||
yellow: colorRamp(chroma("#a06e3b")),
|
yellow: colorRamp(chroma("#a06e3b")),
|
||||||
|
@ -22,7 +24,26 @@ const ramps = {
|
||||||
blue: colorRamp(chroma("#576ddb")),
|
blue: colorRamp(chroma("#576ddb")),
|
||||||
violet: colorRamp(chroma("#955ae7")),
|
violet: colorRamp(chroma("#955ae7")),
|
||||||
magenta: colorRamp(chroma("#bf40bf")),
|
magenta: colorRamp(chroma("#bf40bf")),
|
||||||
};
|
});
|
||||||
|
|
||||||
export const dark = createTheme(`${name}-dark`, false, ramps);
|
export const light = createColorScheme(`${name}-light`, true, {
|
||||||
export const light = createTheme(`${name}-light`, true, ramps);
|
neutral: chroma
|
||||||
|
.scale([
|
||||||
|
"#19171c",
|
||||||
|
"#26232a",
|
||||||
|
"#585260",
|
||||||
|
"#655f6d",
|
||||||
|
"#7e7887",
|
||||||
|
"#8b8792",
|
||||||
|
"#e2dfe7",
|
||||||
|
"#efecf4",
|
||||||
|
]).correctLightness(),
|
||||||
|
red: colorRamp(chroma("#be4678")),
|
||||||
|
orange: colorRamp(chroma("#aa573c")),
|
||||||
|
yellow: colorRamp(chroma("#a06e3b")),
|
||||||
|
green: colorRamp(chroma("#2a9292")),
|
||||||
|
cyan: colorRamp(chroma("#398bc6")),
|
||||||
|
blue: colorRamp(chroma("#576ddb")),
|
||||||
|
violet: colorRamp(chroma("#955ae7")),
|
||||||
|
magenta: colorRamp(chroma("#bf40bf")),
|
||||||
|
});
|
78
styles/src/themes/common/colorScheme.ts
Normal file
78
styles/src/themes/common/colorScheme.ts
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
import { Scale } from "chroma-js";
|
||||||
|
|
||||||
|
export interface ColorScheme {
|
||||||
|
name: string;
|
||||||
|
isLight: boolean;
|
||||||
|
|
||||||
|
lowest: Layer;
|
||||||
|
middle: Layer;
|
||||||
|
highest: Layer;
|
||||||
|
|
||||||
|
ramps: RampSet;
|
||||||
|
|
||||||
|
popoverShadow: Shadow;
|
||||||
|
modalShadow: Shadow;
|
||||||
|
|
||||||
|
players: Players;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Player {
|
||||||
|
cursor: string;
|
||||||
|
selection: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Players {
|
||||||
|
"0": Player;
|
||||||
|
"1": Player;
|
||||||
|
"2": Player;
|
||||||
|
"3": Player;
|
||||||
|
"4": Player;
|
||||||
|
"5": Player;
|
||||||
|
"6": Player;
|
||||||
|
"7": Player;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Shadow {
|
||||||
|
blur: number;
|
||||||
|
color: string;
|
||||||
|
offset: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StyleSets = keyof Layer;
|
||||||
|
export interface Layer {
|
||||||
|
base: StyleSet;
|
||||||
|
variant: StyleSet;
|
||||||
|
on: StyleSet;
|
||||||
|
accent: StyleSet;
|
||||||
|
positive: StyleSet;
|
||||||
|
warning: StyleSet;
|
||||||
|
negative: StyleSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RampSet {
|
||||||
|
neutral: Scale;
|
||||||
|
red: Scale;
|
||||||
|
orange: Scale;
|
||||||
|
yellow: Scale;
|
||||||
|
green: Scale;
|
||||||
|
cyan: Scale;
|
||||||
|
blue: Scale;
|
||||||
|
violet: Scale;
|
||||||
|
magenta: Scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Styles = keyof StyleSet;
|
||||||
|
export interface StyleSet {
|
||||||
|
default: Style;
|
||||||
|
active: Style;
|
||||||
|
disabled: Style;
|
||||||
|
hovered: Style;
|
||||||
|
pressed: Style;
|
||||||
|
inverted: Style;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Style {
|
||||||
|
background: string;
|
||||||
|
border: string;
|
||||||
|
foreground: string;
|
||||||
|
}
|
202
styles/src/themes/common/ramps.ts
Normal file
202
styles/src/themes/common/ramps.ts
Normal file
|
@ -0,0 +1,202 @@
|
||||||
|
import chroma, { Color, Scale } from "chroma-js";
|
||||||
|
import {
|
||||||
|
ColorScheme,
|
||||||
|
Layer,
|
||||||
|
Player,
|
||||||
|
RampSet,
|
||||||
|
Style,
|
||||||
|
Styles,
|
||||||
|
StyleSet,
|
||||||
|
} from "./colorScheme";
|
||||||
|
|
||||||
|
export function colorRamp(color: Color): Scale {
|
||||||
|
let endColor = color.desaturate(1).brighten(5);
|
||||||
|
let startColor = color.desaturate(1).darken(4);
|
||||||
|
return chroma.scale([startColor, color, endColor]).mode("lab");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createColorScheme(
|
||||||
|
name: string,
|
||||||
|
isLight: boolean,
|
||||||
|
colorRamps: { [rampName: string]: Scale }
|
||||||
|
): ColorScheme {
|
||||||
|
// Chromajs scales from 0 to 1 flipped if isLight is true
|
||||||
|
let ramps: RampSet = {} as any;
|
||||||
|
|
||||||
|
// Chromajs mutates the underlying ramp when you call domain. This causes problems because
|
||||||
|
// we now store the ramps object in the theme so that we can pull colors out of them.
|
||||||
|
// So instead of calling domain and storing the result, we have to construct new ramps for each
|
||||||
|
// theme so that we don't modify the passed in ramps.
|
||||||
|
// This combined with an error in the type definitions for chroma js means we have to cast the colors
|
||||||
|
// function to any in order to get the colors back out from the original ramps.
|
||||||
|
if (isLight) {
|
||||||
|
for (var rampName in colorRamps) {
|
||||||
|
(ramps as any)[rampName] = chroma.scale(
|
||||||
|
colorRamps[rampName].colors(100).reverse()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ramps.neutral = chroma.scale(colorRamps.neutral.colors(100).reverse());
|
||||||
|
} else {
|
||||||
|
for (var rampName in colorRamps) {
|
||||||
|
(ramps as any)[rampName] = chroma.scale(colorRamps[rampName].colors(100));
|
||||||
|
}
|
||||||
|
ramps.neutral = chroma.scale(colorRamps.neutral.colors(100));
|
||||||
|
}
|
||||||
|
|
||||||
|
let lowest = lowestLayer(ramps);
|
||||||
|
let middle = middleLayer(ramps);
|
||||||
|
let highest = highestLayer(ramps);
|
||||||
|
|
||||||
|
let popoverShadow = {
|
||||||
|
blur: 4,
|
||||||
|
color: ramps
|
||||||
|
.neutral(isLight ? 7 : 0)
|
||||||
|
.darken()
|
||||||
|
.alpha(0.2)
|
||||||
|
.hex(), // TODO used blend previously. Replace with something else
|
||||||
|
offset: [1, 2],
|
||||||
|
};
|
||||||
|
|
||||||
|
let modalShadow = {
|
||||||
|
blur: 16,
|
||||||
|
color: ramps
|
||||||
|
.neutral(isLight ? 7 : 0)
|
||||||
|
.darken()
|
||||||
|
.alpha(0.2)
|
||||||
|
.hex(), // TODO used blend previously. Replace with something else
|
||||||
|
offset: [0, 2],
|
||||||
|
};
|
||||||
|
|
||||||
|
let players = {
|
||||||
|
"0": player(ramps.blue),
|
||||||
|
"1": player(ramps.green),
|
||||||
|
"2": player(ramps.magenta),
|
||||||
|
"3": player(ramps.orange),
|
||||||
|
"4": player(ramps.violet),
|
||||||
|
"5": player(ramps.cyan),
|
||||||
|
"6": player(ramps.red),
|
||||||
|
"7": player(ramps.yellow),
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
isLight,
|
||||||
|
|
||||||
|
ramps,
|
||||||
|
|
||||||
|
lowest,
|
||||||
|
middle,
|
||||||
|
highest,
|
||||||
|
|
||||||
|
popoverShadow,
|
||||||
|
modalShadow,
|
||||||
|
|
||||||
|
players,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function player(ramp: Scale): Player {
|
||||||
|
return {
|
||||||
|
selection: ramp(0.5).alpha(0.24).hex(),
|
||||||
|
cursor: ramp(0.5).hex(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function lowestLayer(ramps: RampSet): Layer {
|
||||||
|
return {
|
||||||
|
base: buildStyleSet(ramps.neutral, 0.2, 1),
|
||||||
|
variant: buildStyleSet(ramps.neutral, 0.2, 0.7),
|
||||||
|
on: buildStyleSet(ramps.neutral, 0.1, 1),
|
||||||
|
accent: buildStyleSet(ramps.blue, 0.1, 0.5),
|
||||||
|
positive: buildStyleSet(ramps.green, 0.1, 0.5),
|
||||||
|
warning: buildStyleSet(ramps.yellow, 0.1, 0.5),
|
||||||
|
negative: buildStyleSet(ramps.red, 0.1, 0.5),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function middleLayer(ramps: RampSet): Layer {
|
||||||
|
return {
|
||||||
|
base: buildStyleSet(ramps.neutral, 0.1, 1),
|
||||||
|
variant: buildStyleSet(ramps.neutral, 0.1, 0.7),
|
||||||
|
on: buildStyleSet(ramps.neutral, 0, 1),
|
||||||
|
accent: buildStyleSet(ramps.blue, 0.1, 0.5),
|
||||||
|
positive: buildStyleSet(ramps.green, 0.1, 0.5),
|
||||||
|
warning: buildStyleSet(ramps.yellow, 0.1, 0.5),
|
||||||
|
negative: buildStyleSet(ramps.red, 0.1, 0.5),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function highestLayer(ramps: RampSet): Layer {
|
||||||
|
return {
|
||||||
|
base: buildStyleSet(ramps.neutral, 0, 1),
|
||||||
|
variant: buildStyleSet(ramps.neutral, 0, 0.7),
|
||||||
|
on: buildStyleSet(ramps.neutral, 0.1, 1),
|
||||||
|
accent: buildStyleSet(ramps.blue, 0.1, 0.5),
|
||||||
|
positive: buildStyleSet(ramps.green, 0.1, 0.5),
|
||||||
|
warning: buildStyleSet(ramps.yellow, 0.1, 0.5),
|
||||||
|
negative: buildStyleSet(ramps.red, 0.1, 0.5),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildStyleSet(
|
||||||
|
ramp: Scale,
|
||||||
|
backgroundBase: number,
|
||||||
|
foregroundBase: number,
|
||||||
|
step: number = 0.08,
|
||||||
|
): StyleSet {
|
||||||
|
let styleDefinitions = buildStyleDefinition(backgroundBase, foregroundBase, step);
|
||||||
|
|
||||||
|
function colorString(indexOrColor: number | Color): string {
|
||||||
|
if (typeof indexOrColor === "number") {
|
||||||
|
return ramp(indexOrColor).hex();
|
||||||
|
} else {
|
||||||
|
return indexOrColor.hex();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildStyle(style: Styles): Style {
|
||||||
|
return {
|
||||||
|
background: colorString(styleDefinitions.background[style]),
|
||||||
|
border: colorString(styleDefinitions.border[style]),
|
||||||
|
foreground: colorString(styleDefinitions.foreground[style]),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
default: buildStyle("default"),
|
||||||
|
hovered: buildStyle("hovered"),
|
||||||
|
pressed: buildStyle("pressed"),
|
||||||
|
active: buildStyle("active"),
|
||||||
|
disabled: buildStyle("disabled"),
|
||||||
|
inverted: buildStyle("inverted"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildStyleDefinition(bgBase: number, fgBase: number, step: number = 0.08) {
|
||||||
|
return {
|
||||||
|
background: {
|
||||||
|
default: bgBase,
|
||||||
|
hovered: bgBase + step,
|
||||||
|
pressed: bgBase + step * 1.5,
|
||||||
|
active: bgBase + step * 2.2,
|
||||||
|
disabled: bgBase,
|
||||||
|
inverted: fgBase + step * 6,
|
||||||
|
},
|
||||||
|
border: {
|
||||||
|
default: bgBase + step * 1,
|
||||||
|
hovered: bgBase + step,
|
||||||
|
pressed: bgBase + step,
|
||||||
|
active: bgBase + step * 3,
|
||||||
|
disabled: bgBase + step * 0.5,
|
||||||
|
inverted: bgBase - step * 3,
|
||||||
|
},
|
||||||
|
foreground: {
|
||||||
|
default: fgBase,
|
||||||
|
hovered: fgBase,
|
||||||
|
pressed: fgBase,
|
||||||
|
active: fgBase + step * 6,
|
||||||
|
disabled: bgBase + step * 4,
|
||||||
|
inverted: bgBase + step * 2,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
30
styles/src/themes/internal/zed-pro.ts
Normal file
30
styles/src/themes/internal/zed-pro.ts
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import chroma from "chroma-js";
|
||||||
|
import { colorRamp, createColorScheme } from "../common/ramps";
|
||||||
|
|
||||||
|
const name = "zed-pro";
|
||||||
|
|
||||||
|
const ramps = {
|
||||||
|
neutral: chroma
|
||||||
|
.scale([
|
||||||
|
"#101010",
|
||||||
|
"#1C1C1C",
|
||||||
|
"#212121",
|
||||||
|
"#2D2D2D",
|
||||||
|
"#B9B9B9",
|
||||||
|
"#DADADA",
|
||||||
|
"#E6E6E6",
|
||||||
|
"#FFFFFF",
|
||||||
|
])
|
||||||
|
.domain([0, 0.1, 0.2, 0.3, 0.7, 0.8, 0.9, 1]),
|
||||||
|
red: colorRamp(chroma("#DC604F")),
|
||||||
|
orange: colorRamp(chroma("#DE782F")),
|
||||||
|
yellow: colorRamp(chroma("#E0B750")),
|
||||||
|
green: colorRamp(chroma("#2A643D")),
|
||||||
|
cyan: colorRamp(chroma("#215050")),
|
||||||
|
blue: colorRamp(chroma("#2F6DB7")),
|
||||||
|
violet: colorRamp(chroma("#5874C1")),
|
||||||
|
magenta: colorRamp(chroma("#DE9AB8")),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const dark = createColorScheme(`${name}-dark`, false, ramps);
|
||||||
|
export const light = createColorScheme(`${name}-light`, true, ramps);
|
|
@ -1,5 +1,5 @@
|
||||||
import chroma from "chroma-js";
|
import chroma from "chroma-js";
|
||||||
import { colorRamp, createTheme } from "./common/base16";
|
import { colorRamp, createColorScheme } from "./common/ramps";
|
||||||
|
|
||||||
const name = "one";
|
const name = "one";
|
||||||
const author = "Chris Kempson (http://chriskempson.com)";
|
const author = "Chris Kempson (http://chriskempson.com)";
|
||||||
|
@ -24,16 +24,9 @@ const base0E = "#c678dd";
|
||||||
const base0F = "#be5046";
|
const base0F = "#be5046";
|
||||||
|
|
||||||
const ramps = {
|
const ramps = {
|
||||||
neutral: chroma.scale([
|
neutral: chroma
|
||||||
base00,
|
.scale([base00, base01, base02, base03, base04, base05, base06, base07])
|
||||||
base01,
|
.domain([0.05, 0.22, 0.25, 0.45, 0.62, 0.8, 0.9, 1]),
|
||||||
base02,
|
|
||||||
base03,
|
|
||||||
base04,
|
|
||||||
base05,
|
|
||||||
base06,
|
|
||||||
base07,
|
|
||||||
]),
|
|
||||||
red: colorRamp(chroma(base08)),
|
red: colorRamp(chroma(base08)),
|
||||||
orange: colorRamp(chroma(base09)),
|
orange: colorRamp(chroma(base09)),
|
||||||
yellow: colorRamp(chroma(base0A)),
|
yellow: colorRamp(chroma(base0A)),
|
||||||
|
@ -44,4 +37,4 @@ const ramps = {
|
||||||
magenta: colorRamp(chroma(base0F)),
|
magenta: colorRamp(chroma(base0F)),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dark = createTheme(`${name}-dark`, false, ramps);
|
export const dark = createColorScheme(`${name}-dark`, false, ramps);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import chroma from "chroma-js";
|
import chroma from "chroma-js";
|
||||||
import { colorRamp, createTheme } from "./common/base16";
|
import { colorRamp, createColorScheme } from "./common/ramps";
|
||||||
|
|
||||||
const name = "one";
|
const name = "one";
|
||||||
const author = "Daniel Pfeifer (http://github.com/purpleKarrot)";
|
const author = "Daniel Pfeifer (http://github.com/purpleKarrot)";
|
||||||
|
@ -24,16 +24,9 @@ const base0E = "#a626a4";
|
||||||
const base0F = "#986801";
|
const base0F = "#986801";
|
||||||
|
|
||||||
const ramps = {
|
const ramps = {
|
||||||
neutral: chroma.scale([
|
neutral: chroma
|
||||||
base00,
|
.scale([base00, base01, base02, base03, base04, base05, base06, base07])
|
||||||
base01,
|
.domain([0, 0.05, 0.77, 1]),
|
||||||
base02,
|
|
||||||
base03,
|
|
||||||
base04,
|
|
||||||
base05,
|
|
||||||
base06,
|
|
||||||
base07,
|
|
||||||
]),
|
|
||||||
red: colorRamp(chroma(base08)),
|
red: colorRamp(chroma(base08)),
|
||||||
orange: colorRamp(chroma(base09)),
|
orange: colorRamp(chroma(base09)),
|
||||||
yellow: colorRamp(chroma(base0A)),
|
yellow: colorRamp(chroma(base0A)),
|
||||||
|
@ -44,4 +37,4 @@ const ramps = {
|
||||||
magenta: colorRamp(chroma(base0F)),
|
magenta: colorRamp(chroma(base0F)),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const light = createTheme(`${name}-light`, true, ramps);
|
export const light = createColorScheme(`${name}-light`, true, ramps);
|
||||||
|
|
|
@ -1,19 +1,21 @@
|
||||||
import chroma from "chroma-js";
|
import chroma from "chroma-js";
|
||||||
import { colorRamp, createTheme } from "./common/base16";
|
import { colorRamp, createColorScheme } from "./common/ramps";
|
||||||
|
|
||||||
const name = "rosé-pine-dawn";
|
const name = "rosé-pine-dawn";
|
||||||
|
|
||||||
const ramps = {
|
const ramps = {
|
||||||
neutral: chroma.scale([
|
neutral: chroma
|
||||||
"#575279",
|
.scale([
|
||||||
"#797593",
|
"#575279",
|
||||||
"#9893A5",
|
"#797593",
|
||||||
"#B5AFB8",
|
"#9893A5",
|
||||||
"#D3CCCC",
|
"#B5AFB8",
|
||||||
"#F2E9E1",
|
"#D3CCCC",
|
||||||
"#FFFAF3",
|
"#F2E9E1",
|
||||||
"#FAF4ED",
|
"#FFFAF3",
|
||||||
]),
|
"#FAF4ED",
|
||||||
|
])
|
||||||
|
.domain([0, 0.35, 0.45, 0.65, 0.7, 0.8, 0.9, 1]),
|
||||||
red: colorRamp(chroma("#B4637A")),
|
red: colorRamp(chroma("#B4637A")),
|
||||||
orange: colorRamp(chroma("#D7827E")),
|
orange: colorRamp(chroma("#D7827E")),
|
||||||
yellow: colorRamp(chroma("#EA9D34")),
|
yellow: colorRamp(chroma("#EA9D34")),
|
||||||
|
@ -24,4 +26,4 @@ const ramps = {
|
||||||
magenta: colorRamp(chroma("#79549F")),
|
magenta: colorRamp(chroma("#79549F")),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const light = createTheme(`${name}`, true, ramps);
|
export const light = createColorScheme(`${name}`, true, ramps);
|
||||||
|
|
|
@ -1,19 +1,21 @@
|
||||||
import chroma from "chroma-js";
|
import chroma from "chroma-js";
|
||||||
import { colorRamp, createTheme } from "./common/base16";
|
import { colorRamp, createColorScheme } from "./common/ramps";
|
||||||
|
|
||||||
const name = "rosé-pine-moon";
|
const name = "rosé-pine-moon";
|
||||||
|
|
||||||
const ramps = {
|
const ramps = {
|
||||||
neutral: chroma.scale([
|
neutral: chroma
|
||||||
"#232136",
|
.scale([
|
||||||
"#2A273F",
|
"#232136",
|
||||||
"#393552",
|
"#2A273F",
|
||||||
"#3E3A53",
|
"#393552",
|
||||||
"#56526C",
|
"#3E3A53",
|
||||||
"#6E6A86",
|
"#56526C",
|
||||||
"#908CAA",
|
"#6E6A86",
|
||||||
"#E0DEF4",
|
"#908CAA",
|
||||||
]),
|
"#E0DEF4",
|
||||||
|
])
|
||||||
|
.domain([0, 0.3, 0.55, 1]),
|
||||||
red: colorRamp(chroma("#EB6F92")),
|
red: colorRamp(chroma("#EB6F92")),
|
||||||
orange: colorRamp(chroma("#EBBCBA")),
|
orange: colorRamp(chroma("#EBBCBA")),
|
||||||
yellow: colorRamp(chroma("#F6C177")),
|
yellow: colorRamp(chroma("#F6C177")),
|
||||||
|
@ -24,4 +26,4 @@ const ramps = {
|
||||||
magenta: colorRamp(chroma("#AB6FE9")),
|
magenta: colorRamp(chroma("#AB6FE9")),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dark = createTheme(`${name}`, false, ramps);
|
export const dark = createColorScheme(`${name}`, false, ramps);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import chroma from "chroma-js";
|
import chroma from "chroma-js";
|
||||||
import { colorRamp, createTheme } from "./common/base16";
|
import { colorRamp, createColorScheme } from "./common/ramps";
|
||||||
|
|
||||||
const name = "rosé-pine";
|
const name = "rosé-pine";
|
||||||
|
|
||||||
|
@ -24,4 +24,4 @@ const ramps = {
|
||||||
magenta: colorRamp(chroma("#AB6FE9")),
|
magenta: colorRamp(chroma("#AB6FE9")),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dark = createTheme(`${name}`, false, ramps);
|
export const dark = createColorScheme(`${name}`, false, ramps);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import chroma from "chroma-js";
|
import chroma from "chroma-js";
|
||||||
import { colorRamp, createTheme } from "./common/base16";
|
import { colorRamp, createColorScheme } from "./common/ramps";
|
||||||
|
|
||||||
const name = "sandcastle";
|
const name = "sandcastle";
|
||||||
|
|
||||||
|
@ -17,11 +17,11 @@ const ramps = {
|
||||||
red: colorRamp(chroma("#B4637A")),
|
red: colorRamp(chroma("#B4637A")),
|
||||||
orange: colorRamp(chroma("#a07e3b")),
|
orange: colorRamp(chroma("#a07e3b")),
|
||||||
yellow: colorRamp(chroma("#a07e3b")),
|
yellow: colorRamp(chroma("#a07e3b")),
|
||||||
green: colorRamp(chroma("#528b8b")),
|
green: colorRamp(chroma("#83a598")),
|
||||||
cyan: colorRamp(chroma("#83a598")),
|
cyan: colorRamp(chroma("#83a598")),
|
||||||
blue: colorRamp(chroma("#83a598")),
|
blue: colorRamp(chroma("#528b8b")),
|
||||||
violet: colorRamp(chroma("#d75f5f")),
|
violet: colorRamp(chroma("#d75f5f")),
|
||||||
magenta: colorRamp(chroma("#a87322")),
|
magenta: colorRamp(chroma("#a87322")),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dark = createTheme(`${name}`, false, ramps);
|
export const dark = createColorScheme(`${name}`, false, ramps);
|
||||||
|
|
|
@ -1,19 +1,21 @@
|
||||||
import chroma from "chroma-js";
|
import chroma from "chroma-js";
|
||||||
import { colorRamp, createTheme } from "./common/base16";
|
import { colorRamp, createColorScheme } from "./common/ramps";
|
||||||
|
|
||||||
const name = "solarized";
|
const name = "solarized";
|
||||||
|
|
||||||
const ramps = {
|
const ramps = {
|
||||||
neutral: chroma.scale([
|
neutral: chroma
|
||||||
"#002b36",
|
.scale([
|
||||||
"#073642",
|
"#002b36",
|
||||||
"#586e75",
|
"#073642",
|
||||||
"#657b83",
|
"#586e75",
|
||||||
"#839496",
|
"#657b83",
|
||||||
"#93a1a1",
|
"#839496",
|
||||||
"#eee8d5",
|
"#93a1a1",
|
||||||
"#fdf6e3",
|
"#eee8d5",
|
||||||
]),
|
"#fdf6e3",
|
||||||
|
])
|
||||||
|
.domain([0, 0.2, 0.38, 0.45, 0.65, 0.7, 0.85, 1]),
|
||||||
red: colorRamp(chroma("#dc322f")),
|
red: colorRamp(chroma("#dc322f")),
|
||||||
orange: colorRamp(chroma("#cb4b16")),
|
orange: colorRamp(chroma("#cb4b16")),
|
||||||
yellow: colorRamp(chroma("#b58900")),
|
yellow: colorRamp(chroma("#b58900")),
|
||||||
|
@ -24,5 +26,5 @@ const ramps = {
|
||||||
magenta: colorRamp(chroma("#d33682")),
|
magenta: colorRamp(chroma("#d33682")),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dark = createTheme(`${name}-dark`, false, ramps);
|
export const dark = createColorScheme(`${name}-dark`, false, ramps);
|
||||||
export const light = createTheme(`${name}-light`, true, ramps);
|
export const light = createColorScheme(`${name}-light`, true, ramps);
|
||||||
|
|
|
@ -1,19 +1,21 @@
|
||||||
import chroma from "chroma-js";
|
import chroma from "chroma-js";
|
||||||
import { colorRamp, createTheme } from "./common/base16";
|
import { colorRamp, createColorScheme } from "./common/ramps";
|
||||||
|
|
||||||
const name = "sulphurpool";
|
const name = "sulphurpool";
|
||||||
|
|
||||||
const ramps = {
|
const ramps = {
|
||||||
neutral: chroma.scale([
|
neutral: chroma
|
||||||
"#202746",
|
.scale([
|
||||||
"#293256",
|
"#202746",
|
||||||
"#5e6687",
|
"#293256",
|
||||||
"#6b7394",
|
"#5e6687",
|
||||||
"#898ea4",
|
"#6b7394",
|
||||||
"#979db4",
|
"#898ea4",
|
||||||
"#dfe2f1",
|
"#979db4",
|
||||||
"#f5f7ff",
|
"#dfe2f1",
|
||||||
]),
|
"#f5f7ff",
|
||||||
|
])
|
||||||
|
.domain([0, 0.2, 0.38, 0.45, 0.65, 0.7, 0.85, 1]),
|
||||||
red: colorRamp(chroma("#c94922")),
|
red: colorRamp(chroma("#c94922")),
|
||||||
orange: colorRamp(chroma("#c76b29")),
|
orange: colorRamp(chroma("#c76b29")),
|
||||||
yellow: colorRamp(chroma("#c08b30")),
|
yellow: colorRamp(chroma("#c08b30")),
|
||||||
|
@ -24,5 +26,5 @@ const ramps = {
|
||||||
magenta: colorRamp(chroma("#9c637a")),
|
magenta: colorRamp(chroma("#9c637a")),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dark = createTheme(`${name}-dark`, false, ramps);
|
export const dark = createColorScheme(`${name}-dark`, false, ramps);
|
||||||
export const light = createTheme(`${name}-light`, true, ramps);
|
export const light = createColorScheme(`${name}-light`, true, ramps);
|
||||||
|
|
|
@ -1,19 +1,21 @@
|
||||||
import chroma from "chroma-js";
|
import chroma from "chroma-js";
|
||||||
import { colorRamp, createTheme } from "./common/base16";
|
import { colorRamp, createColorScheme } from "./common/ramps";
|
||||||
|
|
||||||
const name = "summercamp";
|
const name = "summercamp";
|
||||||
|
|
||||||
const ramps = {
|
const ramps = {
|
||||||
neutral: chroma.scale([
|
neutral: chroma
|
||||||
"#1c1810",
|
.scale([
|
||||||
"#2a261c",
|
"#1c1810",
|
||||||
"#3a3527",
|
"#2a261c",
|
||||||
"#3a3527",
|
"#3a3527",
|
||||||
"#5f5b45",
|
"#3a3527",
|
||||||
"#736e55",
|
"#5f5b45",
|
||||||
"#bab696",
|
"#736e55",
|
||||||
"#f8f5de",
|
"#bab696",
|
||||||
]),
|
"#f8f5de",
|
||||||
|
])
|
||||||
|
.domain([0, 0.2, 0.38, 0.4, 0.65, 0.7, 0.85, 1]),
|
||||||
red: colorRamp(chroma("#e35142")),
|
red: colorRamp(chroma("#e35142")),
|
||||||
orange: colorRamp(chroma("#fba11b")),
|
orange: colorRamp(chroma("#fba11b")),
|
||||||
yellow: colorRamp(chroma("#f2ff27")),
|
yellow: colorRamp(chroma("#f2ff27")),
|
||||||
|
@ -24,4 +26,4 @@ const ramps = {
|
||||||
magenta: colorRamp(chroma("#F69BE7")),
|
magenta: colorRamp(chroma("#F69BE7")),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dark = createTheme(`${name}`, false, ramps);
|
export const dark = createColorScheme(`${name}`, false, ramps);
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
import chroma from "chroma-js";
|
|
||||||
import { colorRamp, createTheme } from "./common/base16";
|
|
||||||
|
|
||||||
const name = "summerfruit";
|
|
||||||
|
|
||||||
const ramps = {
|
|
||||||
neutral: chroma.scale([
|
|
||||||
"#151515",
|
|
||||||
"#202020",
|
|
||||||
"#303030",
|
|
||||||
"#505050",
|
|
||||||
"#B0B0B0",
|
|
||||||
"#D0D0D0",
|
|
||||||
"#E0E0E0",
|
|
||||||
"#FFFFFF",
|
|
||||||
]),
|
|
||||||
red: colorRamp(chroma("#FF0086")),
|
|
||||||
orange: colorRamp(chroma("#FD8900")),
|
|
||||||
yellow: colorRamp(chroma("#ABA800")),
|
|
||||||
green: colorRamp(chroma("#00C918")),
|
|
||||||
cyan: colorRamp(chroma("#1FAAAA")),
|
|
||||||
blue: colorRamp(chroma("#3777E6")),
|
|
||||||
violet: colorRamp(chroma("#AD00A1")),
|
|
||||||
magenta: colorRamp(chroma("#CC6633")),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const dark = createTheme(`${name}-dark`, false, ramps);
|
|
||||||
export const light = createTheme(`${name}-light`, true, ramps);
|
|
|
@ -3,7 +3,7 @@
|
||||||
**/
|
**/
|
||||||
|
|
||||||
import chroma from "chroma-js";
|
import chroma from "chroma-js";
|
||||||
import { colorRamp, createTheme } from "./common/base16";
|
import { colorRamp, createColorScheme } from "./common/ramps";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Theme Name
|
* Theme Name
|
||||||
|
@ -56,14 +56,14 @@ const ramps = {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Theme Variants
|
* Color Scheme Variants
|
||||||
*
|
*
|
||||||
* Currently we only support (and require) dark and light themes
|
* Currently we only support (and require) dark and light themes
|
||||||
* Eventually you will be able to have only a light or dark theme,
|
* Eventually you will be able to have only a light or dark theme,
|
||||||
* and define other variants here.
|
* and define other variants here.
|
||||||
*
|
*
|
||||||
* createTheme([name], [isLight], [arrayOfRamps])
|
* createColorScheme([name], [isLight], [arrayOfRamps])
|
||||||
**/
|
**/
|
||||||
|
|
||||||
export const dark = createTheme(`${name}-dark`, false, ramps);
|
export const dark = createColorScheme(`${name}-dark`, false, ramps);
|
||||||
export const light = createTheme(`${name}-light`, true, ramps);
|
export const light = createColorScheme(`${name}-light`, true, ramps);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue