Detailed changes
@@ -840,22 +840,6 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "chrono"
version = "0.4.22"
@@ -1519,9 +1503,9 @@ dependencies = [
[[package]]
name = "cxx"
-version = "1.0.78"
+version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "19f39818dcfc97d45b03953c1292efc4e80954e1583c4aa770bac1383e2310a4"
+checksum = "3f83d0ebf42c6eafb8d7c52f7e5f2d3003b89c7aa4fd2b79229209459a849af8"
dependencies = [
"cc",
"cxxbridge-flags",
@@ -1531,9 +1515,9 @@ dependencies = [
[[package]]
name = "cxx-build"
-version = "1.0.78"
+version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3e580d70777c116df50c390d1211993f62d40302881e54d4b79727acb83d0199"
+checksum = "07d050484b55975889284352b0ffc2ecbda25c0c55978017c132b29ba0818a86"
dependencies = [
"cc",
"codespan-reporting",
@@ -1546,15 +1530,15 @@ dependencies = [
[[package]]
name = "cxxbridge-flags"
-version = "1.0.78"
+version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56a46460b88d1cec95112c8c363f0e2c39afdb237f60583b0b36343bf627ea9c"
+checksum = "99d2199b00553eda8012dfec8d3b1c75fce747cf27c169a270b3b99e3448ab78"
[[package]]
name = "cxxbridge-macro"
-version = "1.0.78"
+version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "747b608fecf06b0d72d440f27acc99288207324b793be2c17991839f3d4995ea"
+checksum = "dcb67a6de1f602736dd7eaead0080cf3435df806c61b24b13328db128c58868f"
dependencies = [
"proc-macro2",
"quote",
@@ -1716,12 +1700,9 @@ dependencies = [
[[package]]
name = "dotenvy"
-version = "0.15.5"
+version = "0.15.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed9155c8f4dc55c7470ae9da3f63c6785245093b3f6aeb0f5bf2e968efbba314"
-dependencies = [
- "dirs 4.0.0",
-]
+checksum = "03d8c417d7a8cb362e0c37e5d815f5eb7c37f79ff93707329d5a194e42e54ca0"
[[package]]
name = "drag_and_drop"
@@ -2717,9 +2698,9 @@ dependencies = [
[[package]]
name = "iana-time-zone-haiku"
-version = "0.1.0"
+version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fde6edd6cef363e9359ed3c98ba64590ba9eecba2293eb5a723ab32aee8926aa"
+checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"
dependencies = [
"cxx",
"cxx-build",
@@ -3473,7 +3454,7 @@ dependencies = [
"libc",
"log",
"wasi 0.11.0+wasi-snapshot-preview1",
- "windows-sys",
+ "windows-sys 0.36.1",
]
[[package]]
@@ -3902,7 +3883,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [
"lock_api",
- "parking_lot_core 0.9.3",
+ "parking_lot_core 0.9.4",
]
[[package]]
@@ -3921,15 +3902,15 @@ dependencies = [
[[package]]
name = "parking_lot_core"
-version = "0.9.3"
+version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929"
+checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0"
dependencies = [
"cfg-if 1.0.0",
"libc",
"redox_syscall",
"smallvec",
- "windows-sys",
+ "windows-sys 0.42.0",
]
[[package]]
@@ -4224,9 +4205,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
-version = "1.0.46"
+version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b"
+checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
dependencies = [
"unicode-ident",
]
@@ -4960,9 +4941,9 @@ dependencies = [
[[package]]
name = "rustls"
-version = "0.20.6"
+version = "0.20.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033"
+checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c"
dependencies = [
"log",
"ring",
@@ -5041,7 +5022,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2"
dependencies = [
"lazy_static",
- "windows-sys",
+ "windows-sys 0.36.1",
]
[[package]]
@@ -5652,7 +5633,7 @@ dependencies = [
"paste",
"percent-encoding",
"rand 0.8.5",
- "rustls 0.20.6",
+ "rustls 0.20.7",
"rustls-pemfile",
"serde",
"serde_json",
@@ -5973,6 +5954,18 @@ dependencies = [
"workspace",
]
+[[package]]
+name = "theme_testbench"
+version = "0.1.0"
+dependencies = [
+ "gpui",
+ "project",
+ "settings",
+ "smallvec",
+ "theme",
+ "workspace",
+]
+
[[package]]
name = "thiserror"
version = "1.0.37"
@@ -6159,7 +6152,7 @@ version = "0.23.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59"
dependencies = [
- "rustls 0.20.6",
+ "rustls 0.20.7",
"tokio",
"webpki 0.22.0",
]
@@ -7443,43 +7436,100 @@ version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
dependencies = [
- "windows_aarch64_msvc",
- "windows_i686_gnu",
- "windows_i686_msvc",
- "windows_x86_64_gnu",
- "windows_x86_64_msvc",
+ "windows_aarch64_msvc 0.36.1",
+ "windows_i686_gnu 0.36.1",
+ "windows_i686_msvc 0.36.1",
+ "windows_x86_64_gnu 0.36.1",
+ "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]]
name = "windows_aarch64_msvc"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
+
[[package]]
name = "windows_i686_gnu"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
+
[[package]]
name = "windows_i686_msvc"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
+
[[package]]
name = "windows_x86_64_gnu"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "windows_x86_64_msvc"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "winreg"
version = "0.10.1"
@@ -7566,9 +7616,9 @@ checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
[[package]]
name = "xmlparser"
-version = "0.13.3"
+version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "114ba2b24d2167ef6d67d7d04c8cc86522b87f490025f39f0303b7db5bf5e3d8"
+checksum = "4d25c75bf9ea12c4040a97f829154768bbbce366287e2dc044af160cd79a13fd"
[[package]]
name = "xmlwriter"
@@ -7599,7 +7649,6 @@ dependencies = [
"backtrace",
"breadcrumbs",
"call",
- "chat_panel",
"chrono",
"cli",
"client",
@@ -7658,6 +7707,7 @@ dependencies = [
"text",
"theme",
"theme_selector",
+ "theme_testbench",
"thiserror",
"tiny_http",
"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},
};
use db::Db;
-use futures::{future::LocalBoxFuture, FutureExt, SinkExt, StreamExt, TryStreamExt};
+use futures::{future::LocalBoxFuture, AsyncReadExt, FutureExt, SinkExt, StreamExt, TryStreamExt};
use gpui::{
- actions, serde_json::Value, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle,
- AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle,
- MutableAppContext, Task, View, ViewContext, ViewHandle,
+ actions,
+ serde_json::{self, Value},
+ AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AnyWeakViewHandle, AppContext,
+ AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task, View, ViewContext,
+ ViewHandle,
};
use http::HttpClient;
use lazy_static::lazy_static;
@@ -25,6 +27,7 @@ use parking_lot::RwLock;
use postage::watch;
use rand::prelude::*;
use rpc::proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, RequestMessage};
+use serde::Deserialize;
use std::{
any::TypeId,
collections::HashMap,
@@ -50,6 +53,9 @@ lazy_static! {
pub static ref IMPERSONATE_LOGIN: Option<String> = std::env::var("ZED_IMPERSONATE")
.ok()
.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";
@@ -919,6 +925,37 @@ impl Client {
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(
self: &Arc<Self>,
credentials: &Credentials,
@@ -933,28 +970,7 @@ impl Client {
let http = self.http.clone();
cx.background().spawn(async move {
- let mut rpc_url = format!("{}/rpc", *ZED_SERVER_URL);
- 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 mut rpc_url = Self::get_rpc_url(http).await?;
let rpc_host = rpc_url
.host_str()
.zip(rpc_url.port_or_known_default())
@@ -997,6 +1013,7 @@ impl Client {
let platform = cx.platform();
let executor = cx.background();
let telemetry = self.telemetry.clone();
+ let http = self.http.clone();
executor.clone().spawn(async move {
// 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
@@ -1006,6 +1023,10 @@ impl Client {
let public_key_string =
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.
let server = tiny_http::Server::http("127.0.0.1:0").expect("failed to find open 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<()> {
let conn_id = self.connection_id()?;
self.peer.disconnect(conn_id);
@@ -6,7 +6,5 @@ LIVE_KIT_SERVER = "http://localhost:7880"
LIVE_KIT_KEY = "devkey"
LIVE_KIT_SECRET = "secret"
-# HONEYCOMB_API_KEY=
-# HONEYCOMB_DATASET=
# RUST_LOG=info
# LOG_JSON=true
@@ -65,31 +65,6 @@ spec:
secretKeyRef:
name: database
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
valueFrom:
secretKeyRef:
@@ -101,13 +76,6 @@ spec:
value: ${RUST_LOG}
- name: LOG_JSON
value: "true"
- - name: HONEYCOMB_DATASET
- value: "collab"
- - name: HONEYCOMB_API_KEY
- valueFrom:
- secretKeyRef:
- name: honeycomb
- key: apiKey
securityContext:
capabilities:
# 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();
- if token != state.api_token {
+ if token != state.config.api_token {
Err(Error::Http(
StatusCode::UNAUTHORIZED,
"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)]
struct AuthenticatedUserParams {
- github_user_id: i32,
+ github_user_id: Option<i32>,
github_login: String,
}
@@ -104,7 +104,7 @@ async fn get_authenticated_user(
) -> Result<Json<AuthenticatedUserResponse>> {
let user = app
.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?
.ok_or_else(|| Error::Http(StatusCode::NOT_FOUND, "user not found".into()))?;
let metrics_id = app.db.get_user_metrics_id(user.id).await?;
@@ -156,7 +156,7 @@ async fn create_user(
Json(params): Json<CreateUserParams>,
Extension(app): Extension<Arc<AppState>>,
Extension(rpc_server): Extension<Arc<rpc::Server>>,
-) -> Result<Json<CreateUserResponse>> {
+) -> Result<Json<Option<CreateUserResponse>>> {
let user = NewUserParams {
github_login: params.github_login,
github_user_id: params.github_user_id,
@@ -165,7 +165,8 @@ async fn create_user(
// Creating a user via the normal signup process
let result = if let Some(email_confirmation_code) = params.email_confirmation_code {
- app.db
+ if let Some(result) = app
+ .db
.create_user_from_invite(
&Invite {
email_address: params.email_address,
@@ -174,6 +175,11 @@ async fn create_user(
user,
)
.await?
+ {
+ result
+ } else {
+ return Ok(Json(None));
+ }
}
// Creating a user as an admin
else if params.admin {
@@ -200,11 +206,11 @@ async fn create_user(
.await?
.ok_or_else(|| anyhow!("couldn't find the user we just created"))?;
- Ok(Json(CreateUserResponse {
+ Ok(Json(Some(CreateUserResponse {
user,
metrics_id: result.metrics_id,
signup_device_id: result.signup_device_id,
- }))
+ })))
}
#[derive(Deserialize)]
@@ -1,7 +1,7 @@
-use std::sync::Arc;
-
-use super::db::{self, UserId};
-use crate::{AppState, Error, Result};
+use crate::{
+ db::{self, UserId},
+ AppState, Error, Result,
+};
use anyhow::{anyhow, Context};
use axum::{
http::{self, Request, StatusCode},
@@ -13,6 +13,7 @@ use scrypt::{
password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
Scrypt,
};
+use std::sync::Arc;
pub async fn validate_header<B>(mut req: Request<B>, next: Next<B>) -> impl IntoResponse {
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())
.ok_or_else(|| {
Error::Http(
- StatusCode::BAD_REQUEST,
+ StatusCode::UNAUTHORIZED,
"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;
- for password_hash in state.db.get_access_token_hashes(user_id).await? {
- if verify_access_token(access_token, &password_hash)? {
+ let state = req.extensions().get::<Arc<AppState>>().unwrap();
+ if let Some(admin_token) = access_token.strip_prefix("ADMIN_TOKEN:") {
+ if state.config.api_token == admin_token {
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,
invite: &Invite,
user: NewUserParams,
- ) -> Result<NewUserResult>;
+ ) -> Result<Option<NewUserResult>>;
/// Registers a new project for the given user.
async fn register_project(&self, host_user_id: UserId) -> Result<ProjectId>;
@@ -482,7 +482,7 @@ impl Db for PostgresDb {
&self,
invite: &Invite,
user: NewUserParams,
- ) -> Result<NewUserResult> {
+ ) -> Result<Option<NewUserResult>> {
let mut tx = self.pool.begin().await?;
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()))?;
if existing_user_id.is_some() {
- Err(Error::Http(
- StatusCode::UNPROCESSABLE_ENTITY,
- "invitation already redeemed".to_string(),
- ))?;
+ return Ok(None);
}
let (user_id, metrics_id): (UserId, String) = sqlx::query_as(
@@ -576,12 +573,12 @@ impl Db for PostgresDb {
}
tx.commit().await?;
- Ok(NewUserResult {
+ Ok(Some(NewUserResult {
user_id,
metrics_id,
inviting_user_id,
signup_device_id,
- })
+ }))
}
// invite codes
@@ -1958,7 +1955,7 @@ mod test {
&self,
_invite: &Invite,
_user: NewUserParams,
- ) -> Result<NewUserResult> {
+ ) -> Result<Option<NewUserResult>> {
unimplemented!()
}
@@ -852,6 +852,7 @@ async fn test_invite_codes() {
},
)
.await
+ .unwrap()
.unwrap();
let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
assert_eq!(invite_count, 1);
@@ -897,6 +898,7 @@ async fn test_invite_codes() {
},
)
.await
+ .unwrap()
.unwrap();
let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
assert_eq!(invite_count, 0);
@@ -954,6 +956,7 @@ async fn test_invite_codes() {
)
.await
.unwrap()
+ .unwrap()
.user_id;
let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
@@ -1099,6 +1102,7 @@ async fn test_signups() {
},
)
.await
+ .unwrap()
.unwrap();
let user = db.get_user_by_id(user_id).await.unwrap().unwrap();
assert!(inviting_user_id.is_none());
@@ -1108,19 +1112,21 @@ async fn test_signups() {
assert_eq!(signup_device_id.unwrap(), "device_id_0");
// cannot redeem the same signup again.
- db.create_user_from_invite(
- &Invite {
- 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(),
- github_user_id: 1,
- invite_count: 5,
- },
- )
- .await
- .unwrap_err();
+ assert!(db
+ .create_user_from_invite(
+ &Invite {
+ 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(),
+ github_user_id: 1,
+ invite_count: 5,
+ },
+ )
+ .await
+ .unwrap()
+ .is_none());
// cannot redeem a signup with the wrong confirmation code.
db.create_user_from_invite(
@@ -6478,8 +6478,7 @@ impl TestServer {
Arc::new(AppState {
db: test_db.db().clone(),
live_kit_client: Some(Arc::new(fake_server.create_api_client())),
- api_token: Default::default(),
- invite_link_prefix: Default::default(),
+ config: Default::default(),
})
}
@@ -30,8 +30,6 @@ pub struct Config {
pub database_url: String,
pub api_token: String,
pub invite_link_prefix: String,
- pub honeycomb_api_key: Option<String>,
- pub honeycomb_dataset: Option<String>,
pub live_kit_server: Option<String>,
pub live_kit_key: Option<String>,
pub live_kit_secret: Option<String>,
@@ -41,13 +39,12 @@ pub struct Config {
pub struct AppState {
db: Arc<dyn Db>,
- api_token: String,
- invite_link_prefix: String,
live_kit_client: Option<Arc<dyn live_kit_server::api::Client>>,
+ config: Config,
}
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 live_kit_client = if let Some(((server, key), secret)) = config
.live_kit_server
@@ -67,8 +64,7 @@ impl AppState {
let this = Self {
db: Arc::new(db),
live_kit_client,
- api_token: config.api_token.clone(),
- invite_link_prefix: config.invite_link_prefix.clone(),
+ config,
};
Ok(Arc::new(this))
}
@@ -85,9 +81,9 @@ async fn main() -> Result<()> {
let config = envy::from_env::<Config>().expect("error loading 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");
let rpc_server = rpc::Server::new(state.clone(), None);
@@ -396,7 +396,7 @@ impl Server {
if let Some((code, count)) = invite_code {
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,
})?;
}
@@ -562,7 +562,7 @@ impl Server {
self.peer.send(
connection_id,
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,
},
)?;
@@ -580,7 +580,10 @@ impl Server {
self.peer.send(
connection_id,
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,
},
)?;
@@ -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 rusqlite::{
- named_params, params,
- types::{FromSql, FromSqlError, FromSqlResult, ValueRef},
-};
+use collections::HashSet;
+use rusqlite::{named_params, params};
use super::Db;
@@ -31,7 +29,13 @@ pub enum SerializedItemKind {
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 {
Editor(usize, PathBuf),
Terminal(usize),
@@ -39,27 +43,6 @@ pub enum SerializedItem {
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 {
fn kind(&self) -> SerializedItemKind {
match self {
@@ -82,117 +65,206 @@ impl SerializedItem {
impl Db {
fn write_item(&self, serialized_item: SerializedItem) -> Result<()> {
- let mut lock = self.connection.lock();
- let tx = lock.transaction()?;
-
- // Serialize the item
- let id = serialized_item.id();
- {
- let kind = format!("{:?}", serialized_item.kind());
+ self.real()
+ .map(|db| {
+ let mut lock = db.connection.lock();
+ let tx = lock.transaction()?;
+
+ // Serialize the item
+ let id = serialized_item.id();
+ {
+ let mut stmt = tx.prepare_cached(
+ "INSERT OR REPLACE INTO items(id, kind) VALUES ((?), (?))",
+ )?;
+
+ dbg!("inserting item");
+ stmt.execute(params![id, serialized_item.kind().to_string()])?;
+ }
- let mut stmt =
- tx.prepare_cached("INSERT OR REPLACE INTO items(id, kind) VALUES ((?), (?))")?;
+ // 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 ((?), (?))",
+ )?;
- stmt.execute(params![id, kind])?;
- }
+ let path_bytes = path.as_os_str().as_bytes();
+ stmt.execute(params![id, path_bytes])?;
+ }
+ SerializedItem::ProjectSearch(_, query) => {
+ dbg!("inserting query");
+ let mut stmt = tx.prepare_cached(
+ "INSERT OR REPLACE INTO item_query(item_id, query) VALUES ((?), (?))",
+ )?;
- // Serialize item data
- match &serialized_item {
- SerializedItem::Editor(_, path) => {
- let mut stmt = tx.prepare_cached(
- "INSERT OR REPLACE INTO item_path(item_id, path) VALUES ((?), (?))",
- )?;
+ stmt.execute(params![id, query])?;
+ }
+ _ => {}
+ }
- let path_bytes = path.as_os_str().as_bytes();
- 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 ((?), (?))",
- )?;
+ tx.commit()?;
- stmt.execute(params![id, query])?;
- }
- _ => {}
- }
+ 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)?;
- tx.commit()?;
+ dbg!(zero, one);
+ Ok(())
+ })?
+ .collect::<Vec<Result<(), _>>>();
- Ok(())
+ Ok(())
+ })
+ .unwrap_or(Ok(()))
}
fn delete_item(&self, item_id: usize) -> Result<()> {
- let lock = self.connection.lock();
-
- let mut stmt = lock.prepare_cached(
- "
- DELETE FROM items WHERE id = (:id);
- DELETE FROM item_path WHERE id = (:id);
- DELETE FROM item_query WHERE id = (:id);
- ",
- )?;
+ self.real()
+ .map(|db| {
+ let lock = db.connection.lock();
+
+ let mut stmt = lock.prepare_cached(
+ r#"
+ DELETE FROM items WHERE id = (:id);
+ DELETE FROM item_path 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>> {
- let mut lock = self.connection.lock();
-
- let tx = lock.transaction()?;
-
- // 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.
- let result = {
- let mut read_stmt = tx.prepare_cached(
- "
- SELECT items.id, items.kind, item_path.path, item_query.query
- FROM items
- LEFT JOIN item_path
- ON items.id = item_path.item_id
- LEFT JOIN item_query
- ON items.id = item_query.item_id
- ORDER BY items.id
- ",
- )?;
-
- let result = read_stmt
- .query_map([], |row| {
- let id: usize = row.get(0)?;
- let kind: SerializedItemKind = row.get(1)?;
-
- match kind {
- SerializedItemKind::Editor => {
- let buf: Vec<u8> = row.get(2)?;
+ fn take_items(&self) -> Result<HashSet<SerializedItem>> {
+ self.real()
+ .map(|db| {
+ let mut lock = db.connection.lock();
+
+ let tx = lock.transaction()?;
+
+ // 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.
+ let result = {
+ let mut editors_stmt = tx.prepare_cached(
+ r#"
+ SELECT items.id, item_path.path
+ FROM items
+ LEFT JOIN item_path
+ ON items.id = item_path.item_id
+ WHERE items.kind = ?;
+ "#,
+ )?;
+
+ let editors_iter = editors_stmt.query_map(
+ [SerializedItemKind::Editor.to_string()],
+ |row| {
+ let id: usize = row.get(0)?;
+
+ let buf: Vec<u8> = row.get(1)?;
let path: PathBuf = OsStr::from_bytes(&buf).into();
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(
- "DELETE FROM items;
- DELETE FROM item_path;
- DELETE FROM item_query;",
- )?;
-
- delete_stmt.execute([])?;
-
- result
- };
-
- tx.commit()?;
-
- Ok(result)
+ },
+ )?;
+
+ let mut terminals_stmt = tx.prepare_cached(
+ r#"
+ SELECT items.id
+ FROM items
+ WHERE items.kind = ?;
+ "#,
+ )?;
+ let terminals_iter = terminals_stmt.query_map(
+ [SerializedItemKind::Terminal.to_string()],
+ |row| {
+ let id: usize = row.get(0)?;
+
+ Ok(SerializedItem::Terminal(id))
+ },
+ )?;
+
+ 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)?;
+
+ Ok(SerializedItem::ProjectSearch(id, query))
+ },
+ )?;
+
+ #[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]
fn test_items_round_trip() -> Result<()> {
- let db = Db::open_in_memory()?;
+ let db = Db::open_in_memory();
let mut items = vec![
SerializedItem::Editor(0, PathBuf::from("/tmp/test.txt")),
SerializedItem::Terminal(1),
SerializedItem::ProjectSearch(2, "Test query!".to_string()),
SerializedItem::Diagnostics(3),
- ];
+ ]
+ .into_iter()
+ .collect::<HashSet<_>>();
for item in items.iter() {
+ dbg!("Inserting... ");
db.write_item(item.clone())?;
}
assert_eq!(items, db.take_items()?);
// 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() {
db.write_item(item.clone())?;
}
- items.remove(2);
+ items.remove(&SerializedItem::ProjectSearch(2, "Test query!".to_string()));
db.delete_item(2)?;
assert_eq!(items, db.take_items()?);
@@ -35,9 +35,9 @@ use gpui::{
impl_actions, impl_internal_actions,
platform::CursorStyle,
serde_json::json,
- text_layout, AnyViewHandle, AppContext, AsyncAppContext, ClipboardItem, Element, ElementBox,
- Entity, ModelHandle, MouseButton, MutableAppContext, RenderContext, Subscription, Task, View,
- ViewContext, ViewHandle, WeakViewHandle,
+ text_layout, AnyViewHandle, AppContext, AsyncAppContext, Axis, ClipboardItem, Element,
+ ElementBox, Entity, ModelHandle, MouseButton, MutableAppContext, RenderContext, Subscription,
+ Task, View, ViewContext, ViewHandle, WeakViewHandle,
};
use highlight_matching_bracket::refresh_matching_bracket_highlights;
use hover_popover::{hide_hover, HoverState};
@@ -84,6 +84,7 @@ const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
const MAX_LINE_LEN: usize = 1024;
const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
const MAX_SELECTION_HISTORY_LEN: usize = 1024;
+pub const SCROLL_EVENT_SEPARATION: Duration = Duration::from_millis(28);
pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2);
@@ -94,7 +95,10 @@ pub struct SelectNext {
}
#[derive(Clone, PartialEq)]
-pub struct Scroll(pub Vector2F);
+pub struct Scroll {
+ pub scroll_position: Vector2F,
+ pub axis: Option<Axis>,
+}
#[derive(Clone, PartialEq)]
pub struct Select(pub SelectPhase);
@@ -263,7 +267,7 @@ struct ScrollbarAutoHide(bool);
pub fn init(cx: &mut MutableAppContext) {
cx.add_action(Editor::new_file);
- cx.add_action(|this: &mut Editor, action: &Scroll, cx| this.set_scroll_position(action.0, cx));
+ cx.add_action(Editor::scroll);
cx.add_action(Editor::select);
cx.add_action(Editor::cancel);
cx.add_action(Editor::newline);
@@ -429,6 +433,69 @@ pub type GetFieldEditorTheme = fn(&theme::Theme) -> theme::FieldEditor;
type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
+#[derive(Clone, Copy)]
+pub struct OngoingScroll {
+ last_timestamp: Instant,
+ axis: Option<Axis>,
+}
+
+impl OngoingScroll {
+ fn initial() -> OngoingScroll {
+ OngoingScroll {
+ last_timestamp: Instant::now() - SCROLL_EVENT_SEPARATION,
+ axis: None,
+ }
+ }
+
+ fn update(&mut self, axis: Option<Axis>) {
+ self.last_timestamp = Instant::now();
+ self.axis = axis;
+ }
+
+ pub fn filter(&self, delta: &mut Vector2F) -> Option<Axis> {
+ const UNLOCK_PERCENT: f32 = 1.9;
+ const UNLOCK_LOWER_BOUND: f32 = 6.;
+ let mut axis = self.axis;
+
+ let x = delta.x().abs();
+ let y = delta.y().abs();
+ let duration = Instant::now().duration_since(self.last_timestamp);
+ if duration > SCROLL_EVENT_SEPARATION {
+ //New ongoing scroll will start, determine axis
+ axis = if x <= y {
+ Some(Axis::Vertical)
+ } else {
+ Some(Axis::Horizontal)
+ };
+ } else if x.max(y) >= UNLOCK_LOWER_BOUND {
+ //Check if the current ongoing will need to unlock
+ match axis {
+ Some(Axis::Vertical) => {
+ if x > y && x >= y * UNLOCK_PERCENT {
+ axis = None;
+ }
+ }
+
+ Some(Axis::Horizontal) => {
+ if y > x && y >= x * UNLOCK_PERCENT {
+ axis = None;
+ }
+ }
+
+ None => {}
+ }
+ }
+
+ match axis {
+ Some(Axis::Vertical) => *delta = vec2f(0., delta.y()),
+ Some(Axis::Horizontal) => *delta = vec2f(delta.x(), 0.),
+ None => {}
+ }
+
+ axis
+ }
+}
+
pub struct Editor {
handle: WeakViewHandle<Self>,
buffer: ModelHandle<MultiBuffer>,
@@ -443,6 +510,7 @@ pub struct Editor {
select_larger_syntax_node_stack: Vec<Box<[Selection<usize>]>>,
ime_transaction: Option<TransactionId>,
active_diagnostics: Option<ActiveDiagnosticGroup>,
+ ongoing_scroll: OngoingScroll,
scroll_position: Vector2F,
scroll_top_anchor: Anchor,
autoscroll_request: Option<(Autoscroll, bool)>,
@@ -486,6 +554,7 @@ pub struct EditorSnapshot {
pub display_snapshot: DisplaySnapshot,
pub placeholder_text: Option<Arc<str>>,
is_focused: bool,
+ ongoing_scroll: OngoingScroll,
scroll_position: Vector2F,
scroll_top_anchor: Anchor,
}
@@ -1097,6 +1166,7 @@ impl Editor {
soft_wrap_mode_override: None,
get_field_editor_theme,
project,
+ ongoing_scroll: OngoingScroll::initial(),
scroll_position: Vector2F::zero(),
scroll_top_anchor: Anchor::min(),
autoscroll_request: None,
@@ -1189,6 +1259,7 @@ impl Editor {
EditorSnapshot {
mode: self.mode,
display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
+ ongoing_scroll: self.ongoing_scroll,
scroll_position: self.scroll_position,
scroll_top_anchor: self.scroll_top_anchor.clone(),
placeholder_text: self.placeholder_text.clone(),
@@ -1592,6 +1663,11 @@ impl Editor {
});
}
+ fn scroll(&mut self, action: &Scroll, cx: &mut ViewContext<Self>) {
+ self.ongoing_scroll.update(action.axis);
+ self.set_scroll_position(action.scroll_position, cx);
+ }
+
fn select(&mut self, Select(phase): &Select, cx: &mut ViewContext<Self>) {
self.hide_context_menu(cx);
@@ -423,18 +423,27 @@ impl EditorElement {
return false;
}
+ let line_height = position_map.line_height;
let max_glyph_width = position_map.em_width;
- if !precise {
- delta *= vec2f(max_glyph_width, position_map.line_height);
- }
+
+ let axis = if precise {
+ //Trackpad
+ position_map.snapshot.ongoing_scroll.filter(&mut delta)
+ } else {
+ //Not trackpad
+ delta *= vec2f(max_glyph_width, line_height);
+ None //Resets ongoing scroll
+ };
let scroll_position = position_map.snapshot.scroll_position();
let x = (scroll_position.x() * max_glyph_width - delta.x()) / max_glyph_width;
- let y =
- (scroll_position.y() * position_map.line_height - delta.y()) / position_map.line_height;
+ let y = (scroll_position.y() * line_height - delta.y()) / line_height;
let scroll_position = vec2f(x, y).clamp(Vector2F::zero(), position_map.scroll_max);
- cx.dispatch_action(Scroll(scroll_position));
+ cx.dispatch_action(Scroll {
+ scroll_position,
+ axis,
+ });
true
}
@@ -301,13 +301,15 @@ impl Keystroke {
}
}
+ let key = key.ok_or_else(|| anyhow!("Invalid keystroke `{}`", source))?;
+
Ok(Keystroke {
ctrl,
alt,
shift,
cmd,
function,
- key: key.unwrap(),
+ key,
})
}
@@ -35,7 +35,6 @@ serde = { version = "1.0", features = ["derive"] }
-
[dev-dependencies]
gpui = { path = "../gpui", 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 gpui::color::Color;
-use theme::TerminalColors;
+use theme::TerminalStyle;
///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 {
- let background = if modal {
- colors.modal_background
- } else {
- colors.background
- };
-
+pub fn convert_color(alac_color: &AnsiColor, style: &TerminalStyle) -> Color {
match alac_color {
//Named and theme defined colors
alacritty_terminal::ansi::Color::Named(n) => match n {
- alacritty_terminal::ansi::NamedColor::Black => colors.black,
- alacritty_terminal::ansi::NamedColor::Red => colors.red,
- alacritty_terminal::ansi::NamedColor::Green => colors.green,
- alacritty_terminal::ansi::NamedColor::Yellow => colors.yellow,
- alacritty_terminal::ansi::NamedColor::Blue => colors.blue,
- alacritty_terminal::ansi::NamedColor::Magenta => colors.magenta,
- alacritty_terminal::ansi::NamedColor::Cyan => colors.cyan,
- alacritty_terminal::ansi::NamedColor::White => colors.white,
- alacritty_terminal::ansi::NamedColor::BrightBlack => colors.bright_black,
- alacritty_terminal::ansi::NamedColor::BrightRed => colors.bright_red,
- alacritty_terminal::ansi::NamedColor::BrightGreen => colors.bright_green,
- alacritty_terminal::ansi::NamedColor::BrightYellow => colors.bright_yellow,
- alacritty_terminal::ansi::NamedColor::BrightBlue => colors.bright_blue,
- alacritty_terminal::ansi::NamedColor::BrightMagenta => colors.bright_magenta,
- alacritty_terminal::ansi::NamedColor::BrightCyan => colors.bright_cyan,
- alacritty_terminal::ansi::NamedColor::BrightWhite => colors.bright_white,
- alacritty_terminal::ansi::NamedColor::Foreground => colors.foreground,
- alacritty_terminal::ansi::NamedColor::Background => background,
- alacritty_terminal::ansi::NamedColor::Cursor => colors.cursor,
- alacritty_terminal::ansi::NamedColor::DimBlack => colors.dim_black,
- alacritty_terminal::ansi::NamedColor::DimRed => colors.dim_red,
- alacritty_terminal::ansi::NamedColor::DimGreen => colors.dim_green,
- alacritty_terminal::ansi::NamedColor::DimYellow => colors.dim_yellow,
- alacritty_terminal::ansi::NamedColor::DimBlue => colors.dim_blue,
- alacritty_terminal::ansi::NamedColor::DimMagenta => colors.dim_magenta,
- alacritty_terminal::ansi::NamedColor::DimCyan => colors.dim_cyan,
- alacritty_terminal::ansi::NamedColor::DimWhite => colors.dim_white,
- alacritty_terminal::ansi::NamedColor::BrightForeground => colors.bright_foreground,
- alacritty_terminal::ansi::NamedColor::DimForeground => colors.dim_foreground,
+ alacritty_terminal::ansi::NamedColor::Black => style.black,
+ alacritty_terminal::ansi::NamedColor::Red => style.red,
+ alacritty_terminal::ansi::NamedColor::Green => style.green,
+ alacritty_terminal::ansi::NamedColor::Yellow => style.yellow,
+ alacritty_terminal::ansi::NamedColor::Blue => style.blue,
+ alacritty_terminal::ansi::NamedColor::Magenta => style.magenta,
+ alacritty_terminal::ansi::NamedColor::Cyan => style.cyan,
+ alacritty_terminal::ansi::NamedColor::White => style.white,
+ alacritty_terminal::ansi::NamedColor::BrightBlack => style.bright_black,
+ alacritty_terminal::ansi::NamedColor::BrightRed => style.bright_red,
+ alacritty_terminal::ansi::NamedColor::BrightGreen => style.bright_green,
+ alacritty_terminal::ansi::NamedColor::BrightYellow => style.bright_yellow,
+ alacritty_terminal::ansi::NamedColor::BrightBlue => style.bright_blue,
+ alacritty_terminal::ansi::NamedColor::BrightMagenta => style.bright_magenta,
+ alacritty_terminal::ansi::NamedColor::BrightCyan => style.bright_cyan,
+ alacritty_terminal::ansi::NamedColor::BrightWhite => style.bright_white,
+ alacritty_terminal::ansi::NamedColor::Foreground => style.foreground,
+ alacritty_terminal::ansi::NamedColor::Background => style.background,
+ alacritty_terminal::ansi::NamedColor::Cursor => style.cursor,
+ alacritty_terminal::ansi::NamedColor::DimBlack => style.dim_black,
+ alacritty_terminal::ansi::NamedColor::DimRed => style.dim_red,
+ alacritty_terminal::ansi::NamedColor::DimGreen => style.dim_green,
+ alacritty_terminal::ansi::NamedColor::DimYellow => style.dim_yellow,
+ alacritty_terminal::ansi::NamedColor::DimBlue => style.dim_blue,
+ alacritty_terminal::ansi::NamedColor::DimMagenta => style.dim_magenta,
+ alacritty_terminal::ansi::NamedColor::DimCyan => style.dim_cyan,
+ alacritty_terminal::ansi::NamedColor::DimWhite => style.dim_white,
+ alacritty_terminal::ansi::NamedColor::BrightForeground => style.bright_foreground,
+ alacritty_terminal::ansi::NamedColor::DimForeground => style.dim_foreground,
},
//'True' colors
alacritty_terminal::ansi::Color::Spec(rgb) => Color::new(rgb.r, rgb.g, rgb.b, u8::MAX),
//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.
///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
-pub fn get_color_at_index(index: &usize, colors: &TerminalColors) -> Color {
+pub fn get_color_at_index(index: &usize, style: &TerminalStyle) -> Color {
match index {
//0-15 are the same as the named colors above
- 0 => colors.black,
- 1 => colors.red,
- 2 => colors.green,
- 3 => colors.yellow,
- 4 => colors.blue,
- 5 => colors.magenta,
- 6 => colors.cyan,
- 7 => colors.white,
- 8 => colors.bright_black,
- 9 => colors.bright_red,
- 10 => colors.bright_green,
- 11 => colors.bright_yellow,
- 12 => colors.bright_blue,
- 13 => colors.bright_magenta,
- 14 => colors.bright_cyan,
- 15 => colors.bright_white,
+ 0 => style.black,
+ 1 => style.red,
+ 2 => style.green,
+ 3 => style.yellow,
+ 4 => style.blue,
+ 5 => style.magenta,
+ 6 => style.cyan,
+ 7 => style.white,
+ 8 => style.bright_black,
+ 9 => style.bright_red,
+ 10 => style.bright_green,
+ 11 => style.bright_yellow,
+ 12 => style.bright_blue,
+ 13 => style.bright_magenta,
+ 14 => style.bright_cyan,
+ 15 => style.bright_white,
//16-231 are mapped to their RGB colors on a 0-5 range per channel
16..=231 => {
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
}
//For compatability with the alacritty::Colors interface
- 256 => colors.foreground,
- 257 => colors.background,
- 258 => colors.cursor,
- 259 => colors.dim_black,
- 260 => colors.dim_red,
- 261 => colors.dim_green,
- 262 => colors.dim_yellow,
- 263 => colors.dim_blue,
- 264 => colors.dim_magenta,
- 265 => colors.dim_cyan,
- 266 => colors.dim_white,
- 267 => colors.bright_foreground,
- 268 => colors.black, //'Dim Background', non-standard color
+ 256 => style.foreground,
+ 257 => style.background,
+ 258 => style.cursor,
+ 259 => style.dim_black,
+ 260 => style.dim_red,
+ 261 => style.dim_green,
+ 262 => style.dim_yellow,
+ 263 => style.dim_blue,
+ 264 => style.dim_magenta,
+ 265 => style.dim_cyan,
+ 266 => style.dim_white,
+ 267 => style.bright_foreground,
+ 268 => style.black, //'Dim Background', non-standard color
_ => Color::new(0, 0, 0, 255),
}
}
@@ -603,7 +603,7 @@ impl Terminal {
InternalEvent::ColorRequest(index, format) => {
let color = term.colors()[*index].unwrap_or_else(|| {
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))
}
@@ -44,7 +44,6 @@ impl TerminalContainerContent {
}
pub struct TerminalContainer {
- modal: bool,
pub content: TerminalContainerContent,
associated_directory: Option<PathBuf>,
}
@@ -128,7 +127,6 @@ impl TerminalContainer {
cx.focus(content.handle());
TerminalContainer {
- modal,
content,
associated_directory: working_directory,
}
@@ -141,7 +139,6 @@ impl TerminalContainer {
) -> Self {
let connected_view = cx.add_view(|cx| TerminalView::from_terminal(terminal, modal, cx));
TerminalContainer {
- modal,
content: TerminalContainerContent::Connected(connected_view),
associated_directory: None,
}
@@ -161,17 +158,11 @@ impl View for TerminalContainer {
}
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::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>) {
@@ -179,14 +170,6 @@ impl View for TerminalContainer {
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 {
@@ -152,7 +152,6 @@ impl LayoutRect {
pub struct TerminalElement {
terminal: WeakModelHandle<Terminal>,
view: WeakViewHandle<TerminalView>,
- modal: bool,
focused: bool,
cursor_visible: bool,
}
@@ -161,14 +160,12 @@ impl TerminalElement {
pub fn new(
view: WeakViewHandle<TerminalView>,
terminal: WeakModelHandle<Terminal>,
- modal: bool,
focused: bool,
cursor_visible: bool,
) -> TerminalElement {
TerminalElement {
view,
terminal,
- modal,
focused,
cursor_visible,
}
@@ -182,7 +179,6 @@ impl TerminalElement {
terminal_theme: &TerminalStyle,
text_layout_cache: &TextLayoutCache,
font_cache: &FontCache,
- modal: bool,
hyperlink: Option<(HighlightStyle, &RangeInclusive<Point>)>,
) -> (Vec<LayoutCell>, Vec<LayoutRect>) {
let mut cells = vec![];
@@ -222,7 +218,7 @@ impl TerminalElement {
cur_rect = Some(LayoutRect::new(
Point::new(line_index as i32, cell.point.column.0 as i32),
1,
- convert_color(&bg, &terminal_theme.colors, modal),
+ convert_color(&bg, &terminal_theme),
));
}
}
@@ -231,7 +227,7 @@ impl TerminalElement {
cur_rect = Some(LayoutRect::new(
Point::new(line_index as i32, cell.point.column.0 as i32),
1,
- convert_color(&bg, &terminal_theme.colors, modal),
+ convert_color(&bg, &terminal_theme),
));
}
}
@@ -248,7 +244,6 @@ impl TerminalElement {
terminal_theme,
text_style,
font_cache,
- modal,
hyperlink,
);
@@ -308,11 +303,10 @@ impl TerminalElement {
style: &TerminalStyle,
text_style: &TextStyle,
font_cache: &FontCache,
- modal: bool,
hyperlink: Option<(HighlightStyle, &RangeInclusive<Point>)>,
) -> RunStyle {
let flags = indexed.cell.flags;
- let fg = convert_color(&fg, &style.colors, modal);
+ let fg = convert_color(&fg, &style);
let mut underline = flags
.intersects(Flags::ALL_UNDERLINES)
@@ -574,11 +568,7 @@ impl Element for TerminalElement {
Default::default()
};
- let background_color = if self.modal {
- terminal_theme.colors.modal_background
- } else {
- terminal_theme.colors.background
- };
+ let background_color = terminal_theme.background;
let terminal_handle = self.terminal.upgrade(cx).unwrap();
let last_hovered_hyperlink = terminal_handle.update(cx.app, |terminal, cx| {
@@ -639,7 +629,6 @@ impl Element for TerminalElement {
&terminal_theme,
cx.text_layout_cache,
cx.font_cache(),
- self.modal,
last_hovered_hyperlink
.as_ref()
.map(|(_, range, _)| (link_style, range)),
@@ -655,9 +644,9 @@ impl Element for TerminalElement {
let str_trxt = cursor_char.to_string();
let color = if self.focused {
- terminal_theme.colors.background
+ terminal_theme.background
} else {
- terminal_theme.colors.foreground
+ terminal_theme.foreground
};
cx.text_layout_cache.layout_str(
@@ -691,7 +680,7 @@ impl Element for TerminalElement {
cursor_position,
block_width,
dimensions.line_height,
- terminal_theme.colors.cursor,
+ terminal_theme.cursor,
shape,
text,
)
@@ -321,7 +321,6 @@ impl View for TerminalView {
TerminalElement::new(
cx.handle(),
terminal_handle,
- self.modal,
focused,
self.should_show_cursor(focused, cx),
)
@@ -2,7 +2,7 @@ mod theme_registry;
use gpui::{
color::Color,
- elements::{ContainerStyle, ImageStyle, LabelStyle, TooltipStyle},
+ elements::{ContainerStyle, ImageStyle, LabelStyle, Shadow, TooltipStyle},
fonts::{HighlightStyle, TextStyle},
Border, MouseState,
};
@@ -18,7 +18,6 @@ pub struct Theme {
pub meta: ThemeMeta,
pub workspace: Workspace,
pub context_menu: ContextMenu,
- pub chat_panel: ChatPanel,
pub contacts_popover: ContactsPopover,
pub contact_list: ContactList,
pub contact_finder: ContactFinder,
@@ -35,6 +34,7 @@ pub struct Theme {
pub incoming_call_notification: IncomingCallNotification,
pub tooltip: TooltipStyle,
pub terminal: TerminalStyle,
+ pub color_scheme: ColorScheme,
}
#[derive(Deserialize, Default, Clone)]
@@ -319,18 +319,6 @@ pub struct SidebarItem {
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)]
pub struct ProjectPanel {
#[serde(flatten)]
@@ -773,12 +761,6 @@ pub struct HoverPopover {
#[derive(Clone, Deserialize, Default)]
pub struct TerminalStyle {
- pub colors: TerminalColors,
- pub modal_container: ContainerStyle,
-}
-
-#[derive(Clone, Deserialize, Default)]
-pub struct TerminalColors {
pub black: Color,
pub red: Color,
pub green: Color,
@@ -810,3 +792,67 @@ pub struct TerminalColors {
pub bright_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,
+}
@@ -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"] }
@@ -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" }
breadcrumbs = { path = "../breadcrumbs" }
call = { path = "../call" }
-chat_panel = { path = "../chat_panel" }
cli = { path = "../cli" }
collab_ui = { path = "../collab_ui" }
collections = { path = "../collections" }
@@ -52,6 +51,7 @@ text = { path = "../text" }
terminal = { path = "../terminal" }
theme = { path = "../theme" }
theme_selector = { path = "../theme_selector" }
+theme_testbench = { path = "../theme_testbench" }
util = { path = "../util" }
vim = { path = "../vim" }
workspace = { path = "../workspace" }
@@ -35,12 +35,12 @@ fn main() {
let output = Command::new("npm")
.current_dir("../../styles")
- .args(["run", "build-themes"])
+ .args(["run", "build"])
.output()
.expect("failed to run npm");
if !output.status.success() {
panic!(
- "build-themes script failed {}",
+ "build script failed {}",
String::from_utf8_lossy(&output.stderr)
);
}
@@ -116,7 +116,6 @@ fn main() {
editor::init(cx);
go_to_line::init(cx);
file_finder::init(cx);
- chat_panel::init(cx);
outline::init(cx);
project_symbols::init(cx);
project_panel::init(cx);
@@ -124,6 +123,7 @@ fn main() {
search::init(cx);
vim::init(cx);
terminal::init(cx);
+ theme_testbench::init(cx);
cx.spawn(|cx| watch_themes(fs.clone(), themes.clone(), cx))
.detach();
@@ -441,7 +441,7 @@ async fn watch_themes(
while (events.next().await).is_some() {
let output = Command::new("npm")
.current_dir("styles")
- .args(["run", "build-themes"])
+ .args(["run", "build"])
.output()
.await
.log_err()?;
@@ -449,7 +449,7 @@ async fn watch_themes(
cx.update(|cx| theme_selector::ThemeSelector::reload(themes.clone(), cx))
} else {
eprintln!(
- "build-themes script failed {}",
+ "build script failed {}",
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",
- "version": "1.0.0",
- "description": "",
- "main": "index.js",
- "scripts": {
- "build": "npm run build-themes && npm run build-tokens",
- "build-themes": "ts-node ./src/buildThemes.ts"
- },
- "author": "",
- "license": "ISC",
- "dependencies": {
- "@types/chroma-js": "^2.1.3",
- "@types/node": "^17.0.23",
- "case-anything": "^2.1.10",
- "chroma-js": "^2.4.2",
- "ts-node": "^10.7.0"
- }
+ "name": "styles",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "build": "ts-node ./src/buildThemes.ts"
+ },
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "@types/chroma-js": "^2.1.3",
+ "@types/node": "^17.0.23",
+ "case-anything": "^2.1.10",
+ "chroma-js": "^2.4.2",
+ "ts-node": "^10.7.0"
+ }
}
@@ -2,9 +2,12 @@ import * as fs from "fs";
import * as path from "path";
import { tmpdir } from "os";
import app from "./styleTree/app";
-import themes, { internalThemes, experimentalThemes } from "./themes";
+import colorSchemes, {
+ internalColorSchemes,
+ experimentalColorSchemes,
+} from "./colorSchemes";
import snakeCase from "./utils/snakeCase";
-import Theme from "./themes/common/theme";
+import { ColorScheme } from "./themes/common/colorScheme";
const themeDirectory = `${__dirname}/../../assets/themes`;
const internalDirectory = `${themeDirectory}/internal`;
@@ -16,7 +19,7 @@ function clearThemes(themeDirectory: string) {
for (const file of fs.readdirSync(themeDirectory)) {
if (file.endsWith(".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));
}
}
@@ -27,12 +30,12 @@ clearThemes(themeDirectory);
clearThemes(internalDirectory);
clearThemes(experimentsDirectory);
-function writeThemes(themes: Theme[], outputDirectory: string) {
- for (let theme of themes) {
- let styleTree = snakeCase(app(theme));
+function writeThemes(colorSchemes: ColorScheme[], outputDirectory: string) {
+ for (let colorScheme of colorSchemes) {
+ let styleTree = snakeCase(app(colorScheme));
let styleTreeJSON = JSON.stringify(styleTree, null, 2);
- let tempPath = path.join(tempDirectory, `${theme.name}.json`);
- let outPath = path.join(outputDirectory, `${theme.name}.json`);
+ let tempPath = path.join(tempDirectory, `${colorScheme.name}.json`);
+ let outPath = path.join(outputDirectory, `${colorScheme.name}.json`);
fs.writeFileSync(tempPath, styleTreeJSON);
fs.renameSync(tempPath, outPath);
console.log(`- ${outPath} created`);
@@ -40,6 +43,6 @@ function writeThemes(themes: Theme[], outputDirectory: string) {
}
// Write new themes to theme directory
-writeThemes(themes, themeDirectory);
-writeThemes(internalThemes, internalDirectory);
-writeThemes(experimentalThemes, experimentsDirectory);
+writeThemes(colorSchemes, themeDirectory);
+writeThemes(internalColorSchemes, internalDirectory);
+writeThemes(experimentalColorSchemes, experimentsDirectory);
@@ -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 contactFinder from "./contactFinder";
import contactsPopover from "./contactsPopover";
@@ -18,40 +16,51 @@ import tooltip from "./tooltip";
import terminal from "./terminal";
import contactList from "./contactList";
import incomingCallNotification from "./incomingCallNotification";
+import { ColorScheme } from "../themes/common/colorScheme";
-export const panel = {
- padding: { top: 12, bottom: 12 },
-};
-
-export default function app(theme: Theme): Object {
+export default function app(colorScheme: ColorScheme): Object {
return {
meta: {
- name: theme.name,
- isLight: theme.isLight
+ name: colorScheme.name,
+ isLight: colorScheme.isLight,
},
- picker: picker(theme),
- workspace: workspace(theme),
- contextMenu: contextMenu(theme),
- editor: editor(theme),
- projectDiagnostics: projectDiagnostics(theme),
- commandPalette: commandPalette(theme),
- projectPanel: projectPanel(theme),
- chatPanel: chatPanel(theme),
- contactsPopover: contactsPopover(theme),
- contactList: contactList(theme),
- contactFinder: contactFinder(theme),
- search: search(theme),
+ commandPalette: commandPalette(colorScheme),
+ contactNotification: contactNotification(colorScheme),
+ projectSharedNotification: projectSharedNotification(colorScheme),
+ incomingCallNotification: incomingCallNotification(colorScheme),
+ picker: picker(colorScheme),
+ workspace: workspace(colorScheme),
+ contextMenu: contextMenu(colorScheme),
+ editor: editor(colorScheme),
+ projectDiagnostics: projectDiagnostics(colorScheme),
+ projectPanel: projectPanel(colorScheme),
+ contactsPopover: contactsPopover(colorScheme),
+ contactFinder: contactFinder(colorScheme),
+ contactList: contactList(colorScheme),
+ search: search(colorScheme),
breadcrumbs: {
- ...text(theme, "sans", "secondary"),
+ ...text(colorScheme.highest, "sans", "variant"),
padding: {
left: 6,
},
},
- contactNotification: contactNotification(theme),
- updateNotification: updateNotification(theme),
- projectSharedNotification: projectSharedNotification(theme),
- incomingCallNotification: incomingCallNotification(theme),
- tooltip: tooltip(theme),
- terminal: terminal(theme),
+ updateNotification: updateNotification(colorScheme),
+ tooltip: tooltip(colorScheme),
+ terminal: terminal(colorScheme),
+ colorScheme: {
+ ...colorScheme,
+ 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 { text, backgroundColor, border } from "./components";
+import { ColorScheme } from "../themes/common/colorScheme";
+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 {
keystrokeSpacing: 8,
key: {
- text: text(theme, "mono", "secondary", { size: "xs" }),
- cornerRadius: 4,
- background: backgroundColor(theme, "on300"),
- border: border(theme, "secondary"),
+ text: text(layer, "mono", "variant", "default", { size: "xs" }),
+ cornerRadius: 2,
+ background: background(layer, "on"),
padding: {
- top: 2,
- bottom: 2,
- left: 8,
- right: 8,
+ top: 1,
+ bottom: 1,
+ left: 6,
+ right: 6,
},
margin: {
+ top: 1,
+ bottom: 1,
left: 2,
},
active: {
- text: text(theme, "mono", "active", { size: "xs" }),
+ text: text(layer, "mono", "on", "default", { size: "xs" }),
+ background: withOpacity(background(layer, "on"), 0.2),
},
},
};
@@ -1,104 +1,200 @@
-import Theme, { BackgroundColorSet } from "../themes/common/theme";
import { fontFamilies, fontSizes, FontWeight } from "../common";
+import { Layer, Styles, StyleSets, Style } from "../themes/common/colorScheme";
+
+function isStyleSet(key: any): key is StyleSets {
+ return [
+ "base",
+ "variant",
+ "on",
+ "accent",
+ "positive",
+ "warning",
+ "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 type TextColor = keyof Theme["textColor"];
export function text(
- theme: Theme,
+ layer: Layer,
fontFamily: keyof typeof fontFamilies,
- color: TextColor,
- properties?: {
- size?: keyof typeof fontSizes;
- weight?: FontWeight;
- underline?: boolean;
- }
+ 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"];
+
return {
family: fontFamilies[fontFamily],
- color: theme.textColor[color],
+ color: style.foreground,
...properties,
size,
};
}
-export function textColor(theme: Theme, color: TextColor) {
- return theme.textColor[color];
-}
-export type BorderColor = keyof Theme["borderColor"];
-export interface BorderOptions {
- width?: number;
+export interface Border {
+ color: string;
+ width: number;
top?: boolean;
bottom?: boolean;
left?: boolean;
right?: boolean;
overlay?: boolean;
}
-export function border(
- theme: Theme,
- color: BorderColor,
- options?: BorderOptions
-) {
- return {
- color: borderColor(theme, color),
- width: 1,
- ...options,
- };
-}
-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 interface BorderProperties {
+ width?: number;
+ top?: boolean;
+ bottom?: boolean;
+ left?: boolean;
+ right?: boolean;
+ overlay?: boolean;
}
-export function modalShadow(theme: Theme) {
- return {
- blur: 16,
- color: theme.shadow,
- offset: [0, 2],
- };
-}
+export function border(
+ layer: Layer,
+ styleSet: StyleSets,
+ 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);
-export function popoverShadow(theme: Theme) {
- return {
- blur: 4,
- color: theme.shadow,
- offset: [1, 2],
- };
-}
+ if (typeof styleSetStyleOrProperties === "object") {
+ properties = styleSetStyleOrProperties;
+ }
+ if (typeof styleOrProperties === "object") {
+ properties = styleOrProperties;
+ }
-export function draggedShadow(theme: Theme) {
return {
- blur: 6,
- color: theme.shadow,
- offset: [1, 2],
+ color: style.border,
+ width: 1,
+ ...properties,
};
}
@@ -1,12 +1,14 @@
-import Theme from "../themes/common/theme";
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 contactButton = {
- background: backgroundColor(theme, 100),
- color: iconColor(theme, "primary"),
+ background: background(layer, "variant"),
+ color: foreground(layer, "variant"),
iconWidth: 8,
buttonWidth: 16,
cornerRadius: 8,
@@ -15,17 +17,17 @@ export default function contactFinder(theme: Theme) {
return {
picker: {
item: {
- ...picker(theme).item,
+ ...picker(colorScheme).item,
margin: { left: sideMargin, right: sideMargin }
},
- empty: picker(theme).empty,
+ empty: picker(colorScheme).empty,
inputEditor: {
- background: backgroundColor(theme, 500),
+ background: background(layer, "on"),
cornerRadius: 6,
- text: text(theme, "mono", "primary"),
- placeholderText: text(theme, "mono", "placeholder", { size: "sm" }),
- selection: player(theme, 1).selection,
- border: border(theme, "secondary"),
+ text: text(layer, "mono",),
+ placeholderText: text(layer, "mono", "variant", { size: "sm" }),
+ selection: colorScheme.players[0],
+ border: border(layer),
padding: {
bottom: 4,
left: 8,
@@ -51,13 +53,13 @@ export default function contactFinder(theme: Theme) {
contactButton: {
...contactButton,
hover: {
- background: backgroundColor(theme, 100, "hovered"),
+ background: background(layer, "variant", "hovered"),
},
},
disabledContactButton: {
...contactButton,
- background: backgroundColor(theme, 100),
- color: iconColor(theme, "muted"),
+ background: background(layer, "disabled"),
+ color: foreground(layer, "disabled"),
},
};
}
@@ -1,13 +1,21 @@
-import Theme from "../themes/common/theme";
-import { backgroundColor, border, borderColor, iconColor, player, text } from "./components";
+import { ColorScheme } from "../themes/common/colorScheme";
+import {
+ background,
+ border,
+ borderColor,
+ foreground,
+ text,
+} from "./components";
-export default function contactList(theme: Theme) {
+export default function contactsPanel(colorScheme: ColorScheme) {
const nameMargin = 8;
const sidePadding = 12;
+ let layer = colorScheme.middle;
+
const contactButton = {
- background: backgroundColor(theme, 100),
- color: iconColor(theme, "primary"),
+ background: background(layer, "on"),
+ color: foreground(layer, "on"),
iconWidth: 8,
buttonWidth: 16,
cornerRadius: 8,
@@ -20,7 +28,7 @@ export default function contactList(theme: Theme) {
width: 14,
},
name: {
- ...text(theme, "mono", "placeholder", { size: "sm" }),
+ ...text(layer, "mono", { size: "sm" }),
margin: {
left: nameMargin,
right: 6,
@@ -39,13 +47,15 @@ export default function contactList(theme: Theme) {
};
return {
+ background: background(layer),
+ padding: { top: 12, bottom: 0 },
userQueryEditor: {
- background: backgroundColor(theme, 500),
+ background: background(layer, "on"),
cornerRadius: 6,
- text: text(theme, "mono", "primary"),
- placeholderText: text(theme, "mono", "placeholder", { size: "sm" }),
- selection: player(theme, 1).selection,
- border: border(theme, "secondary"),
+ text: text(layer, "mono", "on"),
+ placeholderText: text(layer, "mono", "on", "disabled", { size: "sm" }),
+ selection: colorScheme.players[0],
+ border: border(layer, "on"),
padding: {
bottom: 4,
left: 8,
@@ -58,27 +68,28 @@ export default function contactList(theme: Theme) {
},
userQueryEditorHeight: 33,
addContactButton: {
- color: iconColor(theme, "primary"),
+ margin: { left: 6, right: 12 },
+ color: foreground(layer, "on"),
buttonWidth: 28,
iconWidth: 16,
},
rowHeight: 28,
sectionIconSize: 8,
headerRow: {
- ...text(theme, "mono", "secondary", { size: "sm" }),
- margin: { top: 6 },
+ ...text(layer, "mono", { size: "sm" }),
+ margin: { top: 14 },
padding: {
left: sidePadding,
right: sidePadding,
},
active: {
- ...text(theme, "mono", "primary", { size: "sm" }),
- background: backgroundColor(theme, 100, "active"),
+ ...text(layer, "mono", "active", { size: "sm" }),
+ background: background(layer, "active"),
},
},
leaveCall: {
- background: backgroundColor(theme, 100),
- border: border(theme, "secondary"),
+ background: background(layer),
+ border: border(layer),
cornerRadius: 6,
margin: {
top: 1,
@@ -89,11 +100,11 @@ export default function contactList(theme: Theme) {
left: 7,
right: 7,
},
- ...text(theme, "sans", "secondary", { size: "xs" }),
+ ...text(layer, "sans", "variant", { size: "xs" }),
hover: {
- ...text(theme, "sans", "active", { size: "xs" }),
- background: backgroundColor(theme, "on300", "hovered"),
- border: border(theme, "primary"),
+ ...text(layer, "sans", "hovered", { size: "xs" }),
+ background: background(layer, "hovered"),
+ border: border(layer, "hovered"),
},
},
contactRow: {
@@ -102,7 +113,7 @@ export default function contactList(theme: Theme) {
right: sidePadding,
},
active: {
- background: backgroundColor(theme, 100, "active"),
+ background: background(layer, "active"),
},
},
contactAvatar: {
@@ -113,16 +124,16 @@ export default function contactList(theme: Theme) {
cornerRadius: 4,
padding: 4,
margin: { top: 12, left: 12 },
- background: iconColor(theme, "ok"),
+ background: foreground(layer, "positive"),
},
contactStatusBusy: {
cornerRadius: 4,
padding: 4,
margin: { top: 12, left: 12 },
- background: iconColor(theme, "error"),
+ background: foreground(layer, "negative"),
},
contactUsername: {
- ...text(theme, "mono", "primary", { size: "sm" }),
+ ...text(layer, "mono", { size: "sm" }),
margin: {
left: nameMargin,
},
@@ -131,39 +142,39 @@ export default function contactList(theme: Theme) {
contactButton: {
...contactButton,
hover: {
- background: backgroundColor(theme, "on300", "hovered"),
+ background: background(layer, "hovered"),
},
},
disabledButton: {
...contactButton,
- background: backgroundColor(theme, 100),
- color: iconColor(theme, "muted"),
+ background: background(layer, "on"),
+ color: foreground(layer, "on"),
},
callingIndicator: {
- ...text(theme, "mono", "muted", { size: "xs" })
+ ...text(layer, "mono", "variant", { size: "xs" })
},
treeBranch: {
- color: borderColor(theme, "active"),
+ color: borderColor(layer),
width: 1,
hover: {
- color: borderColor(theme, "active"),
+ color: borderColor(layer),
},
active: {
- color: borderColor(theme, "active"),
+ color: borderColor(layer),
},
},
projectRow: {
...projectRow,
- background: backgroundColor(theme, 300),
+ background: background(layer, "on"),
name: {
...projectRow.name,
- ...text(theme, "mono", "secondary", { size: "sm" }),
+ ...text(layer, "mono", { size: "sm" }),
},
hover: {
- background: backgroundColor(theme, 300, "hovered"),
+ background: background(layer, "on", "hovered"),
},
active: {
- background: backgroundColor(theme, 300, "active"),
+ background: background(layer, "on", "active"),
},
},
}
@@ -1,10 +1,11 @@
-import Theme from "../themes/common/theme";
-import { backgroundColor, iconColor, text } from "./components";
+import { ColorScheme } from "../themes/common/colorScheme";
+import { background, foreground, text } from "./components";
const avatarSize = 12;
const headerPadding = 8;
-export default function contactNotification(theme: Theme): Object {
+export default function contactNotification(colorScheme: ColorScheme): Object {
+ let layer = colorScheme.lowest;
return {
headerAvatar: {
height: avatarSize,
@@ -12,32 +13,32 @@ export default function contactNotification(theme: Theme): Object {
cornerRadius: 6,
},
headerMessage: {
- ...text(theme, "sans", "primary", { size: "xs" }),
+ ...text(layer, "sans", { size: "xs" }),
margin: { left: headerPadding, right: headerPadding },
},
headerHeight: 18,
bodyMessage: {
- ...text(theme, "sans", "secondary", { size: "xs" }),
+ ...text(layer, "sans", { size: "xs" }),
margin: { left: avatarSize + headerPadding, top: 6, bottom: 6 },
},
button: {
- ...text(theme, "sans", "primary", { size: "xs" }),
- background: backgroundColor(theme, "on300"),
+ ...text(layer, "sans", "on", { size: "xs" }),
+ background: background(layer, "on"),
padding: 4,
cornerRadius: 6,
margin: { left: 6 },
hover: {
- background: backgroundColor(theme, "on300", "hovered"),
+ background: background(layer, "on", "hovered"),
},
},
dismissButton: {
- color: iconColor(theme, "secondary"),
+ color: foreground(layer, "on"),
iconWidth: 8,
iconHeight: 8,
buttonWidth: 8,
buttonHeight: 8,
hover: {
- color: iconColor(theme, "primary"),
+ color: foreground(layer, "on", "hovered"),
},
},
};
@@ -1,15 +1,16 @@
-import Theme from "../themes/common/theme";
-import { backgroundColor, border, borderColor, popoverShadow, text } from "./components";
+import { ColorScheme } from "../themes/common/colorScheme";
+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;
return {
- background: backgroundColor(theme, 300, "base"),
+ background: background(layer),
cornerRadius: 6,
padding: { top: 6 },
margin: { top: -6 },
- shadow: popoverShadow(theme),
- border: border(theme, "primary"),
+ shadow: colorScheme.popoverShadow,
+ border: border(layer),
width: 300,
height: 400,
inviteRowHeight: 28,
@@ -18,10 +19,10 @@ export default function contactsPopover(theme: Theme) {
left: sidePadding,
right: sidePadding,
},
- border: { top: true, width: 1, color: borderColor(theme, "primary") },
- text: text(theme, "sans", "secondary", { size: "sm" }),
+ border: border(layer, { top: true }),
+ text: text(layer, "sans", "variant", { size: "sm" }),
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 {
- backgroundColor,
- border,
- borderColor,
- popoverShadow,
- text,
-} from "./components";
+import { ColorScheme } from "../themes/common/colorScheme";
+import { background, border, borderColor, text } from "./components";
-export default function contextMenu(theme: Theme) {
+export default function contextMenu(colorScheme: ColorScheme) {
+ let layer = colorScheme.middle;
return {
- background: backgroundColor(theme, 300, "base"),
- cornerRadius: 6,
- padding: 6,
- shadow: popoverShadow(theme),
- border: border(theme, "primary"),
+ background: background(layer),
+ cornerRadius: 10,
+ padding: 4,
+ shadow: colorScheme.popoverShadow,
+ border: border(layer),
keystrokeMargin: 30,
item: {
iconSpacing: 8,
iconWidth: 14,
- padding: { left: 4, right: 4, top: 2, bottom: 2 },
+ padding: { left: 6, right: 6, top: 2, bottom: 2 },
cornerRadius: 6,
- label: text(theme, "sans", "primary", { size: "sm" }),
+ label: text(layer, "sans", { size: "sm" }),
keystroke: {
- ...text(theme, "sans", "muted", { size: "sm", weight: "bold" }),
+ ...text(layer, "sans", "variant", { size: "sm", weight: "bold" }),
padding: { left: 3, right: 3 },
},
hover: {
- background: backgroundColor(theme, 300, "hovered"),
- text: text(theme, "sans", "primary", { size: "sm" }),
+ background: background(layer, "hovered"),
+ label: text(layer, "sans", "hovered", { size: "sm" }),
},
active: {
- background: backgroundColor(theme, 300, "active"),
- text: text(theme, "sans", "active", { size: "sm" }),
+ background: background(layer, "active"),
+ label: text(layer, "sans", "active", { size: "sm" }),
},
activeHover: {
- background: backgroundColor(theme, 300, "hovered"),
- text: text(theme, "sans", "active", { size: "sm" }),
+ background: background(layer, "active"),
+ label: text(layer, "sans", "active", { size: "sm" }),
},
},
separator: {
- background: borderColor(theme, "primary"),
+ background: borderColor(layer),
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 {
- backgroundColor,
+ ColorScheme,
+ Layer,
+ StyleSets,
+} from "../themes/common/colorScheme";
+import {
+ background,
border,
borderColor,
- iconColor,
- player,
- popoverShadow,
+ foreground,
text,
- textColor,
- TextColor,
} from "./components";
import hoverPopover from "./hoverPopover";
-export default function editor(theme: Theme) {
+export default function editor(colorScheme: ColorScheme) {
+ let layer = colorScheme.highest;
+
const autocompleteItem = {
cornerRadius: 6,
padding: {
@@ -24,17 +27,17 @@ export default function editor(theme: Theme) {
},
};
- function diagnostic(theme: Theme, color: TextColor) {
+ function diagnostic(layer: Layer, styleSet: StyleSets) {
return {
textScaleFactor: 0.857,
header: {
- border: border(theme, "primary", {
+ border: border(layer, {
top: true,
}),
},
message: {
- text: text(theme, "sans", color, { size: "sm" }),
- highlightText: text(theme, "sans", color, {
+ text: text(layer, "sans", styleSet, "inverted", { size: "sm" }),
+ highlightText: text(layer, "sans", styleSet, "inverted", {
size: "sm",
weight: "bold",
}),
@@ -42,121 +45,207 @@ export default function editor(theme: Theme) {
};
}
- const syntax: any = {};
- for (const syntaxKey in theme.syntax) {
- const style = theme.syntax[syntaxKey];
- syntax[syntaxKey] = {
- color: style.color,
- weight: style.weight,
- underline: style.underline,
- italic: style.italic,
- };
- }
+ const syntax = {
+ primary: {
+ color: colorScheme.ramps.neutral(1).hex(),
+ weight: fontWeights.normal,
+ },
+ comment: {
+ color: colorScheme.ramps.neutral(0.71).hex(),
+ 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 {
- textColor: theme.syntax.primary.color,
- background: backgroundColor(theme, 500),
- activeLineBackground: theme.editor.line.active,
+ textColor: syntax.primary.color,
+ background: background(layer),
+ activeLineBackground: background(layer, "on"),
+ highlightedLineBackground: background(layer, "on"),
codeActions: {
- indicator: iconColor(theme, "secondary"),
- verticalScale: 0.618
+ indicator: foreground(layer, "variant"),
+ verticalScale: 0.55,
},
diff: {
- deleted: theme.iconColor.error,
- inserted: theme.iconColor.ok,
- modified: theme.iconColor.warning,
+ deleted: foreground(layer, "negative"),
+ modified: foreground(layer, "warning"),
+ inserted: foreground(layer, "positive"),
removedWidthEm: 0.275,
widthEm: 0.16,
cornerRadius: 0.05,
},
- documentHighlightReadBackground: theme.editor.highlight.occurrence,
- documentHighlightWriteBackground: theme.editor.highlight.activeOccurrence,
- errorColor: theme.textColor.error,
- gutterBackground: backgroundColor(theme, 500),
+ documentHighlightReadBackground: colorScheme.ramps
+ .neutral(0.5)
+ .alpha(0.2)
+ .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,
- highlightedLineBackground: theme.editor.line.highlighted,
- lineNumber: theme.editor.gutter.primary,
- lineNumberActive: theme.editor.gutter.active,
+ lineNumber: foreground(layer, "disabled"),
+ lineNumberActive: foreground(layer),
renameFade: 0.6,
unnecessaryCodeFade: 0.5,
- selection: player(theme, 1).selection,
+ selection: colorScheme.players[0],
guestSelections: [
- player(theme, 2).selection,
- player(theme, 3).selection,
- player(theme, 4).selection,
- player(theme, 5).selection,
- player(theme, 6).selection,
- player(theme, 7).selection,
- player(theme, 8).selection,
+ colorScheme.players[1],
+ colorScheme.players[2],
+ colorScheme.players[3],
+ colorScheme.players[4],
+ colorScheme.players[5],
+ colorScheme.players[6],
+ colorScheme.players[7],
],
autocomplete: {
- background: backgroundColor(theme, 500),
+ background: background(colorScheme.middle),
cornerRadius: 8,
padding: 4,
- border: border(theme, "secondary"),
- shadow: popoverShadow(theme),
+ margin: {
+ left: -14,
+ },
+ border: border(colorScheme.middle),
+ shadow: colorScheme.popoverShadow,
+ matchHighlight: foreground(colorScheme.middle, "accent"),
item: autocompleteItem,
hoveredItem: {
...autocompleteItem,
- background: backgroundColor(theme, 500, "hovered"),
- },
- margin: {
- left: -14,
+ matchHighlight: foreground(colorScheme.middle, "accent", "hovered"),
+ background: background(colorScheme.middle, "hovered"),
},
- matchHighlight: text(theme, "mono", "feature"),
selectedItem: {
...autocompleteItem,
- background: backgroundColor(theme, 500, "active"),
+ matchHighlight: foreground(colorScheme.middle, "accent", "active"),
+ background: background(colorScheme.middle, "active"),
},
},
diagnosticHeader: {
- background: backgroundColor(theme, 300),
+ background: background(colorScheme.middle),
iconWidthFactor: 1.5,
- textScaleFactor: 0.857, // NateQ: Will we need dynamic sizing for text? If so let's create tokens for these.
- border: border(theme, "secondary", {
+ textScaleFactor: 0.857,
+ border: border(colorScheme.middle, {
bottom: true,
top: true,
}),
code: {
- ...text(theme, "mono", "secondary", { size: "sm" }),
+ ...text(colorScheme.middle, "mono", { size: "sm" }),
margin: {
left: 10,
},
},
message: {
- highlightText: text(theme, "sans", "primary", {
+ highlightText: text(colorScheme.middle, "sans", {
size: "sm",
weight: "bold",
}),
- text: text(theme, "sans", "secondary", { size: "sm" }),
+ text: text(colorScheme.middle, "sans", { size: "sm" }),
},
},
diagnosticPathHeader: {
- background: theme.editor.line.active,
+ background: background(colorScheme.middle),
textScaleFactor: 0.857,
- filename: text(theme, "mono", "primary", { size: "sm" }),
+ filename: text(colorScheme.middle, "mono", { size: "sm" }),
path: {
- ...text(theme, "mono", "muted", { size: "sm" }),
+ ...text(colorScheme.middle, "mono", { size: "sm" }),
margin: {
left: 12,
},
},
},
- errorDiagnostic: diagnostic(theme, "error"),
- warningDiagnostic: diagnostic(theme, "warning"),
- informationDiagnostic: diagnostic(theme, "info"),
- hintDiagnostic: diagnostic(theme, "info"),
- invalidErrorDiagnostic: diagnostic(theme, "secondary"),
- invalidHintDiagnostic: diagnostic(theme, "secondary"),
- invalidInformationDiagnostic: diagnostic(theme, "secondary"),
- invalidWarningDiagnostic: diagnostic(theme, "secondary"),
- hoverPopover: hoverPopover(theme),
+ errorDiagnostic: diagnostic(colorScheme.middle, "negative"),
+ warningDiagnostic: diagnostic(colorScheme.middle, "warning"),
+ informationDiagnostic: diagnostic(colorScheme.middle, "accent"),
+ hintDiagnostic: diagnostic(colorScheme.middle, "warning"),
+ invalidErrorDiagnostic: diagnostic(colorScheme.middle, "base"),
+ invalidHintDiagnostic: diagnostic(colorScheme.middle, "base"),
+ invalidInformationDiagnostic: diagnostic(colorScheme.middle, "base"),
+ invalidWarningDiagnostic: diagnostic(colorScheme.middle, "base"),
+ hoverPopover: hoverPopover(colorScheme),
linkDefinition: {
- color: theme.syntax.linkUri.color,
- underline: theme.syntax.linkUri.underline,
+ color: syntax.linkUri.color,
+ underline: syntax.linkUri.underline,
},
jumpIcon: {
- color: iconColor(theme, "secondary"),
+ color: foreground(layer, "on"),
iconWidth: 20,
buttonWidth: 20,
cornerRadius: 6,
@@ -167,32 +256,28 @@ export default function editor(theme: Theme) {
right: 6,
},
hover: {
- color: iconColor(theme, "active"),
- background: backgroundColor(theme, "on500"),
+ color: foreground(layer, "on", "hovered"),
+ background: background(layer, "on", "hovered"),
},
},
scrollbar: {
width: 12,
minHeightFactor: 1.0,
track: {
- border: {
- left: true,
- width: 1,
- color: borderColor(theme, "secondary"),
- },
+ border: border(layer, "variant", { left: true }),
},
thumb: {
- background: withOpacity(borderColor(theme, "secondary"), 0.5),
+ background: withOpacity(borderColor(layer, "variant"), 0.5),
border: {
width: 1,
- color: withOpacity(borderColor(theme, 'muted'), 0.5),
+ color: withOpacity(borderColor(layer, 'variant'), 0.5),
}
}
},
compositionMark: {
underline: {
thickness: 1.0,
- color: borderColor(theme, "active")
+ color: borderColor(layer),
},
},
syntax,
@@ -1,18 +1,19 @@
-import Theme from "../themes/common/theme";
-import { backgroundColor, border, popoverShadow, text } from "./components";
+import { ColorScheme } from "../themes/common/colorScheme";
+import { background, border, text } from "./components";
-export default function HoverPopover(theme: Theme) {
+export default function HoverPopover(colorScheme: ColorScheme) {
+ let layer = colorScheme.middle;
let baseContainer = {
- background: backgroundColor(theme, "on500"),
+ background: background(layer),
cornerRadius: 8,
padding: {
left: 8,
right: 8,
top: 4,
- bottom: 4
+ bottom: 4,
},
- shadow: popoverShadow(theme),
- border: border(theme, "secondary"),
+ shadow: colorScheme.popoverShadow,
+ border: border(layer),
margin: {
left: -8,
},
@@ -22,32 +23,23 @@ export default function HoverPopover(theme: Theme) {
container: baseContainer,
infoContainer: {
...baseContainer,
- background: backgroundColor(theme, "on500Info"),
- border: {
- color: theme.ramps.blue(0).hex(),
- width: 1,
- },
+ background: background(layer, "accent"),
+ border: border(layer, "accent"),
},
warningContainer: {
...baseContainer,
- background: backgroundColor(theme, "on500Warning"),
- border: {
- color: theme.ramps.yellow(0).hex(),
- width: 1,
- },
+ background: background(layer, "warning"),
+ border: border(layer, "warning"),
},
errorContainer: {
...baseContainer,
- background: backgroundColor(theme, "on500Error"),
- border: {
- color: theme.ramps.red(0).hex(),
- width: 1,
- }
+ background: background(layer, "negative"),
+ border: border(layer, "negative"),
},
block_style: {
padding: { top: 4 },
},
- prose: text(theme, "sans", "primary", { size: "sm" }),
- highlight: theme.editor.highlight.occurrence,
+ prose: text(layer, "sans", { size: "sm" }),
+ 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 { backgroundColor, borderColor, text } from "./components";
+import { ColorScheme } from "../themes/common/colorScheme";
+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;
return {
windowHeight: 74,
windowWidth: 380,
- background: backgroundColor(theme, 300),
+ background: background(layer),
callerContainer: {
padding: 12,
},
@@ -19,26 +20,26 @@ export default function incomingCallNotification(theme: Theme): Object {
margin: { left: 10 },
},
callerUsername: {
- ...text(theme, "sans", "active", { size: "sm", weight: "bold" }),
+ ...text(layer, "sans", { size: "sm", weight: "bold" }),
margin: { top: -3 },
},
callerMessage: {
- ...text(theme, "sans", "secondary", { size: "xs" }),
+ ...text(layer, "sans", "variant", { size: "xs" }),
margin: { top: -3 },
},
worktreeRoots: {
- ...text(theme, "sans", "secondary", { size: "xs", weight: "bold" }),
+ ...text(layer, "sans", "variant", { size: "xs", weight: "bold" }),
margin: { top: -3 },
},
buttonWidth: 96,
acceptButton: {
- background: backgroundColor(theme, "ok", "active"),
- border: { left: true, bottom: true, width: 1, color: borderColor(theme, "primary") },
- ...text(theme, "sans", "ok", { size: "xs", weight: "extra_bold" })
+ background: background(layer, "accent"),
+ border: border(layer, { left: true, bottom: true }),
+ ...text(layer, "sans", "positive", { size: "xs", weight: "extra_bold" })
},
declineButton: {
- border: { left: true, width: 1, color: borderColor(theme, "primary") },
- ...text(theme, "sans", "error", { size: "xs", weight: "extra_bold" })
+ border: border(layer, { left: true }),
+ ...text(layer, "sans", "negative", { size: "xs", weight: "extra_bold" })
},
};
}
@@ -1,17 +1,16 @@
-import Theme from "../themes/common/theme";
-import {
- backgroundColor,
- border,
- player,
- modalShadow,
- text,
-} from "./components";
+import { ColorScheme } from "../themes/common/colorScheme";
+import { background, border, text } from "./components";
-export default function picker(theme: Theme) {
+export default function picker(colorScheme: ColorScheme) {
+ let layer = colorScheme.lowest;
return {
- background: backgroundColor(theme, 300),
- cornerRadius: 8,
- padding: 8,
+ background: background(layer),
+ border: border(layer),
+ shadow: colorScheme.modalShadow,
+ cornerRadius: 12,
+ padding: {
+ bottom: 4,
+ },
item: {
padding: {
bottom: 4,
@@ -19,41 +18,48 @@ export default function picker(theme: Theme) {
right: 12,
top: 4,
},
+ margin: {
+ top: 1,
+ left: 4,
+ right: 4,
+ },
cornerRadius: 8,
- text: text(theme, "sans", "secondary"),
- highlightText: text(theme, "sans", "feature", { weight: "bold" }),
+ text: text(layer, "sans", "variant"),
+ highlightText: text(layer, "sans", "accent", { weight: "bold" }),
active: {
- background: backgroundColor(theme, 300, "active"),
- text: text(theme, "sans", "active"),
+ background: background(layer, "base", "active"),
+ text: text(layer, "sans", "base", "active"),
+ highlightText: text(layer, "sans", "accent", {
+ weight: "bold",
+ }),
},
hover: {
- background: backgroundColor(theme, 300, "hovered"),
+ background: background(layer, "hovered"),
},
},
- border: border(theme, "primary"),
empty: {
- text: text(theme, "sans", "muted"),
+ text: text(layer, "sans", "variant"),
padding: {
- bottom: 4,
- left: 12,
- right: 12,
+ bottom: 8,
+ left: 16,
+ right: 16,
top: 8,
},
},
inputEditor: {
- background: backgroundColor(theme, 500),
- cornerRadius: 8,
- placeholderText: text(theme, "sans", "placeholder"),
- selection: player(theme, 1).selection,
- text: text(theme, "mono", "primary"),
- border: border(theme, "secondary"),
+ placeholderText: text(layer, "sans", "on", "disabled"),
+ selection: colorScheme.players[0],
+ text: text(layer, "mono", "on"),
+ border: border(layer, { bottom: true }),
padding: {
- bottom: 7,
+ bottom: 8,
left: 16,
right: 16,
- top: 7,
+ top: 8,
+ },
+ margin: {
+ bottom: 4,
},
},
- shadow: modalShadow(theme),
};
}
@@ -1,12 +1,13 @@
-import Theme from "../themes/common/theme";
-import { backgroundColor, text } from "./components";
+import { ColorScheme } from "../themes/common/colorScheme";
+import { background, text } from "./components";
-export default function projectDiagnostics(theme: Theme) {
+export default function projectDiagnostics(colorScheme: ColorScheme) {
+ let layer = colorScheme.highest;
return {
- background: backgroundColor(theme, 500),
+ background: background(layer),
tabIconSpacing: 4,
tabIconWidth: 13,
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 { panel } from "./app";
-import { backgroundColor, iconColor, player, text } from "./components";
+import { ColorScheme } from "../themes/common/colorScheme";
+import { background, foreground, text } from "./components";
-export default function projectPanel(theme: Theme) {
+export default function projectPanel(colorScheme: ColorScheme) {
+ let layer = colorScheme.middle;
return {
- ...panel,
+ background: background(layer),
padding: { left: 12, right: 12, top: 6, bottom: 6 },
indentWidth: 8,
entry: {
height: 24,
- iconColor: iconColor(theme, "muted"),
+ iconColor: foreground(layer, "variant"),
iconSize: 8,
iconSpacing: 8,
- text: text(theme, "mono", "secondary", { size: "sm" }),
+ text: text(layer, "mono", "variant", { size: "sm" }),
hover: {
- background: backgroundColor(theme, 300, "hovered"),
+ background: background(layer, "variant", "hovered"),
},
active: {
- background: backgroundColor(theme, 300, "active"),
- text: text(theme, "mono", "active", { size: "sm" }),
+ background: background(layer, "active"),
+ text: text(layer, "mono", "active", { size: "sm" }),
},
activeHover: {
- background: backgroundColor(theme, 300, "active"),
- text: text(theme, "mono", "active", { size: "sm" }),
+ background: background(layer, "active"),
+ text: text(layer, "mono", "active", { size: "sm" }),
},
},
cutEntryFade: 0.4,
ignoredEntryFade: 0.6,
filenameEditor: {
- background: backgroundColor(theme, "on300"),
- text: text(theme, "mono", "active", { size: "sm" }),
- selection: player(theme, 1).selection,
+ background: background(layer, "on"),
+ text: text(layer, "mono", "on", { size: "sm" }),
+ selection: colorScheme.players[0],
},
};
}
@@ -1,12 +1,14 @@
-import Theme from "../themes/common/theme";
-import { backgroundColor, borderColor, text } from "./components";
+import { ColorScheme } from "../themes/common/colorScheme";
+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;
return {
windowHeight: 74,
windowWidth: 380,
- background: backgroundColor(theme, 300),
+ background: background(layer),
ownerContainer: {
padding: 12,
},
@@ -19,26 +21,26 @@ export default function projectSharedNotification(theme: Theme): Object {
margin: { left: 10 },
},
ownerUsername: {
- ...text(theme, "sans", "active", { size: "sm", weight: "bold" }),
+ ...text(layer, "sans", { size: "sm", weight: "bold" }),
margin: { top: -3 },
},
message: {
- ...text(theme, "sans", "secondary", { size: "xs" }),
+ ...text(layer, "sans", "variant", { size: "xs" }),
margin: { top: -3 },
},
worktreeRoots: {
- ...text(theme, "sans", "secondary", { size: "xs", weight: "bold" }),
+ ...text(layer, "sans", "variant", { size: "xs", weight: "bold" }),
margin: { top: -3 },
},
buttonWidth: 96,
openButton: {
- background: backgroundColor(theme, "info", "active"),
- border: { left: true, bottom: true, width: 1, color: borderColor(theme, "primary") },
- ...text(theme, "sans", "info", { size: "xs", weight: "extra_bold" })
+ background: background(layer, "accent"),
+ border: border(layer, { left: true, bottom: true, }),
+ ...text(layer, "sans", "accent", { size: "xs", weight: "extra_bold" })
},
dismissButton: {
- border: { left: true, width: 1, color: borderColor(theme, "primary") },
- ...text(theme, "sans", "secondary", { size: "xs", weight: "extra_bold" })
+ border: border(layer, { left: true }),
+ ...text(layer, "sans", "variant", { size: "xs", weight: "extra_bold" })
},
};
}
@@ -1,17 +1,19 @@
-import Theme from "../themes/common/theme";
-import { backgroundColor, border, player, text } from "./components";
+import { ColorScheme } from "../themes/common/colorScheme";
+import { background, border, text } from "./components";
+
+export default function search(colorScheme: ColorScheme) {
+ let layer = colorScheme.highest;
-export default function search(theme: Theme) {
// Search input
const editor = {
- background: backgroundColor(theme, 500),
+ background: background(layer),
cornerRadius: 8,
minWidth: 200,
maxWidth: 500,
- placeholderText: text(theme, "mono", "placeholder"),
- selection: player(theme, 1).selection,
- text: text(theme, "mono", "active"),
- border: border(theme, "secondary"),
+ placeholderText: text(layer, "mono", "disabled"),
+ selection: colorScheme.players[0],
+ text: text(layer, "mono", "default"),
+ border: border(layer),
margin: {
right: 12,
},
@@ -24,14 +26,14 @@ export default function search(theme: Theme) {
};
return {
- matchBackground: theme.editor.highlight.match,
+ matchBackground: background(layer), // theme.editor.highlight.match,
tabIconSpacing: 8,
tabIconWidth: 14,
optionButton: {
- ...text(theme, "mono", "secondary"),
- background: backgroundColor(theme, "on500"),
+ ...text(layer, "mono", "on"),
+ background: background(layer, "on"),
cornerRadius: 6,
- border: border(theme, "secondary"),
+ border: border(layer, "on"),
margin: {
right: 4,
},
@@ -42,28 +44,28 @@ export default function search(theme: Theme) {
top: 2,
},
active: {
- ...text(theme, "mono", "active"),
- background: backgroundColor(theme, "on500", "active"),
- border: border(theme, "muted"),
+ ...text(layer, "mono", "on", "inverted"),
+ background: background(layer, "on", "inverted"),
+ border: border(layer, "on", "inverted"),
},
clicked: {
- ...text(theme, "mono", "active"),
- background: backgroundColor(theme, "on300", "active"),
- border: border(theme, "secondary"),
+ ...text(layer, "mono", "on", "pressed"),
+ background: background(layer, "on", "pressed"),
+ border: border(layer, "on", "pressed"),
},
hover: {
- ...text(theme, "mono", "active"),
- background: backgroundColor(theme, "on500", "hovered"),
- border: border(theme, "muted"),
+ ...text(layer, "mono", "on", "hovered"),
+ background: background(layer, "on", "hovered"),
+ border: border(layer, "on", "hovered"),
},
},
editor,
invalidEditor: {
...editor,
- border: border(theme, "error"),
+ border: border(layer, "negative"),
},
matchIndex: {
- ...text(theme, "mono", "muted"),
+ ...text(layer, "mono", "variant"),
padding: 6,
},
optionButtonGroup: {
@@ -73,7 +75,7 @@ export default function search(theme: Theme) {
},
},
resultsStatus: {
- ...text(theme, "mono", "primary"),
+ ...text(layer, "mono", "on"),
size: 18,
},
};
@@ -1,8 +1,9 @@
-import Theme from "../themes/common/theme";
-import { backgroundColor, border, iconColor, text } from "./components";
-import { workspaceBackground } from "./workspace";
+import { ColorScheme } from "../themes/common/colorScheme";
+import { background, border, foreground, text } from "./components";
+
+export default function statusBar(colorScheme: ColorScheme) {
+ let layer = colorScheme.lowest;
-export default function statusBar(theme: Theme) {
const statusContainer = {
cornerRadius: 6,
padding: { top: 3, bottom: 3, left: 6, right: 6 },
@@ -22,70 +23,70 @@ export default function statusBar(theme: Theme) {
left: 6,
right: 6,
},
- border: border(theme, "primary", { top: true, overlay: true }),
- cursorPosition: text(theme, "sans", "secondary"),
- autoUpdateProgressMessage: text(theme, "sans", "secondary"),
- autoUpdateDoneMessage: text(theme, "sans", "secondary"),
+ border: border(layer, { top: true, overlay: true }),
+ cursorPosition: text(layer, "sans", "variant"),
+ autoUpdateProgressMessage: text(layer, "sans", "variant"),
+ autoUpdateDoneMessage: text(layer, "sans", "variant"),
lspStatus: {
...diagnosticStatusContainer,
iconSpacing: 4,
iconWidth: 14,
height: 18,
- message: text(theme, "sans", "secondary"),
- iconColor: iconColor(theme, "muted"),
+ message: text(layer, "sans"),
+ iconColor: foreground(layer),
hover: {
- message: text(theme, "sans", "primary"),
- iconColor: iconColor(theme, "primary"),
- background: backgroundColor(theme, 300, "hovered"),
+ message: text(layer, "sans"),
+ iconColor: foreground(layer),
+ background: background(layer),
},
},
diagnosticMessage: {
- ...text(theme, "sans", "secondary"),
- hover: text(theme, "sans", "active"),
+ ...text(layer, "sans"),
+ hover: text(layer, "sans", "hovered"),
},
feedback: {
- ...text(theme, "sans", "secondary"),
- hover: text(theme, "sans", "active"),
+ ...text(layer, "sans", "variant"),
+ hover: text(layer, "sans", "hovered"),
},
diagnosticSummary: {
- height: 16,
+ height: 20,
iconWidth: 16,
iconSpacing: 2,
summarySpacing: 6,
- text: text(theme, "sans", "primary", { size: "sm" }),
- iconColorOk: iconColor(theme, "muted"),
- iconColorWarning: iconColor(theme, "warning"),
- iconColorError: iconColor(theme, "error"),
+ text: text(layer, "sans", { size: "sm" }),
+ iconColorOk: foreground(layer, "variant"),
+ iconColorWarning: foreground(layer, "warning"),
+ iconColorError: foreground(layer, "negative"),
containerOk: {
cornerRadius: 6,
padding: { top: 3, bottom: 3, left: 7, right: 7 },
},
containerWarning: {
...diagnosticStatusContainer,
- background: backgroundColor(theme, "warning"),
- border: border(theme, "warning"),
+ background: background(layer, "warning"),
+ border: border(layer, "warning"),
},
containerError: {
...diagnosticStatusContainer,
- background: backgroundColor(theme, "error"),
- border: border(theme, "error"),
+ background: background(layer, "negative"),
+ border: border(layer, "negative"),
},
hover: {
- iconColorOk: iconColor(theme, "active"),
+ iconColorOk: foreground(layer, "on"),
containerOk: {
cornerRadius: 6,
padding: { top: 3, bottom: 3, left: 7, right: 7 },
- background: backgroundColor(theme, 300, "hovered"),
+ background: background(layer, "on", "hovered"),
},
containerWarning: {
...diagnosticStatusContainer,
- background: backgroundColor(theme, "warning", "hovered"),
- border: border(theme, "warning"),
+ background: background(layer, "warning", "hovered"),
+ border: border(layer, "warning", "hovered"),
},
containerError: {
...diagnosticStatusContainer,
- background: backgroundColor(theme, "error", "hovered"),
- border: border(theme, "error"),
+ background: background(layer, "negative", "hovered"),
+ border: border(layer, "negative", "hovered"),
},
},
},
@@ -95,22 +96,22 @@ export default function statusBar(theme: Theme) {
item: {
...statusContainer,
iconSize: 16,
- iconColor: iconColor(theme, "muted"),
+ iconColor: foreground(layer, "variant"),
hover: {
- iconColor: iconColor(theme, "active"),
- background: backgroundColor(theme, 300, "hovered"),
+ iconColor: foreground(layer, "hovered"),
+ background: background(layer, "variant"),
},
active: {
- iconColor: iconColor(theme, "active"),
- background: backgroundColor(theme, 300, "active"),
+ iconColor: foreground(layer, "active"),
+ background: background(layer, "active"),
},
},
badge: {
cornerRadius: 3,
padding: 2,
margin: { bottom: -1, right: -1 },
- border: { width: 1, color: workspaceBackground(theme) },
- background: iconColor(theme, "feature"),
+ border: border(layer),
+ 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 { 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;
+ let activeLayer = colorScheme.highest;
+ let layer = colorScheme.middle;
+
const tab = {
height,
- background: backgroundColor(theme, 300),
- border: border(theme, "primary", {
- left: true,
+ text: text(layer, "sans", "variant", { size: "sm" }),
+ background: background(layer),
+ border: border(layer, {
+ right: true,
bottom: 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: {
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: {
- margin: { left: 6, top: 1 },
- ...text(theme, "sans", "muted", { size: "2xs" })
- }
+ margin: { left: 8 },
+ ...text(layer, "sans", "disabled", { size: "2xs" }),
+ },
};
const activePaneActiveTab = {
...tab,
- background: backgroundColor(theme, 500),
- text: text(theme, "sans", "active", { size: "sm" }),
+ background: background(activeLayer),
+ text: text(activeLayer, "sans", "active", { size: "sm" }),
border: {
...tab.border,
- bottom: false
+ bottom: false,
},
};
const inactivePaneInactiveTab = {
...tab,
- background: backgroundColor(theme, 300),
- text: text(theme, "sans", "muted", { size: "sm" }),
+ background: background(layer),
+ text: text(layer, "sans", "variant", { size: "sm" }),
};
const inactivePaneActiveTab = {
...tab,
- background: backgroundColor(theme, 500),
- text: text(theme, "sans", "secondary", { size: "sm" }),
+ background: background(activeLayer),
+ text: text(layer, "sans", "variant", { size: "sm" }),
border: {
...tab.border,
- bottom: false
+ bottom: false,
},
- }
+ };
const draggedTab = {
...activePaneActiveTab,
- background: withOpacity(tab.background, 0.8),
- border: undefined as any, // Remove border
- shadow: draggedShadow(theme),
- }
+ background: withOpacity(tab.background, 0.95),
+ border: undefined as any,
+ shadow: colorScheme.popoverShadow,
+ };
return {
height,
- background: backgroundColor(theme, 300),
- dropTargetOverlayColor: withOpacity(theme.textColor.muted, 0.6),
- border: border(theme, "primary", {
- bottom: true,
- overlay: true,
- }),
+ background: background(layer),
+ dropTargetOverlayColor: withOpacity(
+ foreground(layer),
+ 0.6
+ ),
activePane: {
activeTab: activePaneActiveTab,
inactiveTab: tab,
@@ -81,11 +89,11 @@ export default function tabBar(theme: Theme) {
},
draggedTab,
paneButton: {
- color: iconColor(theme, "secondary"),
+ color: foreground(layer, "variant"),
iconWidth: 12,
buttonWidth: activePaneActiveTab.height,
hover: {
- color: iconColor(theme, "active"),
+ color: foreground(layer, "hovered"),
},
},
paneButtonContainer: {
@@ -93,7 +101,7 @@ export default function tabBar(theme: Theme) {
border: {
...tab.border,
right: false,
- }
- }
- }
-}
+ },
+ },
+ };
+}
@@ -1,65 +1,52 @@
-import Theme from "../themes/common/theme";
-import { border, modalShadow, player } from "./components";
+import { ColorScheme } from "../themes/common/colorScheme";
-export default function terminal(theme: Theme) {
+export default function terminal(colorScheme: ColorScheme) {
/**
- * Colors are controlled per-cell in the terminal grid.
- * Cells can be set to any of these more 'theme-capable' colors
- * or can be set directly with RGB values.
- * Here are the common interpretations of these names:
- * https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
- */
- let colors = {
- black: theme.ramps.neutral(0).hex(),
- red: theme.ramps.red(0.5).hex(),
- green: theme.ramps.green(0.5).hex(),
- yellow: theme.ramps.yellow(0.5).hex(),
- blue: theme.ramps.blue(0.5).hex(),
- magenta: theme.ramps.magenta(0.5).hex(),
- cyan: theme.ramps.cyan(0.5).hex(),
- white: theme.ramps.neutral(7).hex(),
- brightBlack: theme.ramps.neutral(4).hex(),
- brightRed: theme.ramps.red(0.25).hex(),
- brightGreen: theme.ramps.green(0.25).hex(),
- brightYellow: theme.ramps.yellow(0.25).hex(),
- brightBlue: theme.ramps.blue(0.25).hex(),
- brightMagenta: theme.ramps.magenta(0.25).hex(),
- brightCyan: theme.ramps.cyan(0.25).hex(),
- brightWhite: theme.ramps.neutral(7).hex(),
+ * Colors are controlled per-cell in the terminal grid.
+ * Cells can be set to any of these more 'theme-capable' colors
+ * or can be set directly with RGB values.
+ * Here are the common interpretations of these names:
+ * https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
+ */
+ return {
+ black: colorScheme.ramps.neutral(0).hex(),
+ red: colorScheme.ramps.red(0.5).hex(),
+ green: colorScheme.ramps.green(0.5).hex(),
+ yellow: colorScheme.ramps.yellow(0.5).hex(),
+ blue: colorScheme.ramps.blue(0.5).hex(),
+ magenta: colorScheme.ramps.magenta(0.5).hex(),
+ cyan: colorScheme.ramps.cyan(0.5).hex(),
+ white: colorScheme.ramps.neutral(1).hex(),
+ brightBlack: colorScheme.ramps.neutral(0.4).hex(),
+ brightRed: colorScheme.ramps.red(0.25).hex(),
+ brightGreen: colorScheme.ramps.green(0.25).hex(),
+ brightYellow: colorScheme.ramps.yellow(0.25).hex(),
+ brightBlue: colorScheme.ramps.blue(0.25).hex(),
+ brightMagenta: colorScheme.ramps.magenta(0.25).hex(),
+ brightCyan: colorScheme.ramps.cyan(0.25).hex(),
+ brightWhite: colorScheme.ramps.neutral(1).hex(),
/**
* 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
*/
- background: theme.ramps.neutral(0).hex(),
- modalBackground: theme.ramps.neutral(1).hex(),
+ background: colorScheme.ramps.neutral(0).hex(),
+ modalBackground: colorScheme.ramps.neutral(0.1).hex(),
/**
* Default color for the cursor
*/
- cursor: player(theme, 1).selection.cursor,
- dimBlack: theme.ramps.neutral(7).hex(),
- dimRed: theme.ramps.red(0.75).hex(),
- dimGreen: theme.ramps.green(0.75).hex(),
- dimYellow: theme.ramps.yellow(0.75).hex(),
- dimBlue: theme.ramps.blue(0.75).hex(),
- dimMagenta: theme.ramps.magenta(0.75).hex(),
- dimCyan: theme.ramps.cyan(0.75).hex(),
- dimWhite: theme.ramps.neutral(5).hex(),
- brightForeground: theme.ramps.neutral(7).hex(),
- dimForeground: theme.ramps.neutral(0).hex(),
- };
-
- return {
- colors,
- modalContainer: {
- background: colors.modalBackground,
- cornerRadius: 8,
- padding: 8,
- margin: 25,
- border: border(theme, "primary"),
- shadow: modalShadow(theme),
- },
+ cursor: colorScheme.players[0].cursor,
+ dimBlack: colorScheme.ramps.neutral(1).hex(),
+ dimRed: colorScheme.ramps.red(0.75).hex(),
+ dimGreen: colorScheme.ramps.green(0.75).hex(),
+ dimYellow: colorScheme.ramps.yellow(0.75).hex(),
+ dimBlue: colorScheme.ramps.blue(0.75).hex(),
+ dimMagenta: colorScheme.ramps.magenta(0.75).hex(),
+ dimCyan: colorScheme.ramps.cyan(0.75).hex(),
+ dimWhite: colorScheme.ramps.neutral(0.6).hex(),
+ brightForeground: colorScheme.ramps.neutral(1).hex(),
+ dimForeground: colorScheme.ramps.neutral(0).hex(),
};
}
@@ -1,21 +1,22 @@
-import Theme from "../themes/common/theme";
-import { backgroundColor, border, popoverShadow, text } from "./components";
+import { ColorScheme } from "../themes/common/colorScheme";
+import { background, border, text } from "./components";
-export default function tooltip(theme: Theme) {
+export default function tooltip(colorScheme: ColorScheme) {
+ let layer = colorScheme.middle;
return {
- background: backgroundColor(theme, 500),
- border: border(theme, "secondary"),
+ background: background(layer),
+ border: border(layer),
padding: { top: 4, bottom: 4, left: 8, right: 8 },
margin: { top: 6, left: 6 },
- shadow: popoverShadow(theme),
+ shadow: colorScheme.popoverShadow,
cornerRadius: 6,
- text: text(theme, "sans", "primary", { size: "xs" }),
+ text: text(layer, "sans", { size: "xs" }),
keystroke: {
- background: backgroundColor(theme, "on500"),
+ background: background(layer, "on"),
cornerRadius: 4,
margin: { left: 6 },
padding: { left: 4, right: 4 },
- ...text(theme, "mono", "secondary", { size: "xs", weight: "bold" }),
+ ...text(layer, "mono", "on", { size: "xs", weight: "bold" }),
},
maxTextWidth: 200,
};
@@ -1,29 +1,30 @@
-import Theme from "../themes/common/theme";
-import { iconColor, text } from "./components";
+import { ColorScheme } from "../themes/common/colorScheme";
+import { foreground, text } from "./components";
const headerPadding = 8;
-export default function updateNotification(theme: Theme): Object {
+export default function updateNotification(colorScheme: ColorScheme): Object {
+ let layer = colorScheme.middle;
return {
message: {
- ...text(theme, "sans", "primary", { size: "xs" }),
+ ...text(layer, "sans", { size: "xs" }),
margin: { left: headerPadding, right: headerPadding },
},
actionMessage: {
- ...text(theme, "sans", "secondary", { size: "xs" }),
+ ...text(layer, "sans", { size: "xs" }),
margin: { left: headerPadding, top: 6, bottom: 6 },
hover: {
- color: theme.textColor["active"],
+ color: foreground(layer, "hovered"),
},
},
dismissButton: {
- color: iconColor(theme, "secondary"),
+ color: foreground(layer),
iconWidth: 8,
iconHeight: 8,
buttonWidth: 8,
buttonHeight: 8,
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 {
- backgroundColor,
+ background,
border,
- iconColor,
- modalShadow,
+ borderColor,
+ foreground,
text,
} from "./components";
import statusBar from "./statusBar";
import tabBar from "./tabBar";
-export function workspaceBackground(theme: Theme) {
- return backgroundColor(theme, 300);
-}
-
-export default function workspace(theme: Theme) {
+export default function workspace(colorScheme: ColorScheme) {
+ const layer = colorScheme.lowest;
const titlebarPadding = 6;
const titlebarButton = {
- background: backgroundColor(theme, 100),
- border: border(theme, "secondary"),
cornerRadius: 6,
- margin: {
- top: 1,
- },
padding: {
top: 1,
bottom: 1,
- left: 7,
- right: 7,
+ left: 8,
+ right: 8,
},
- ...text(theme, "sans", "secondary", { size: "xs" }),
+ ...text(layer, "sans", "variant", { size: "xs" }),
+ background: background(layer, "variant"),
+ border: border(layer),
hover: {
- ...text(theme, "sans", "active", { size: "xs" }),
- background: backgroundColor(theme, "on300", "hovered"),
- border: border(theme, "primary"),
+ ...text(layer, "sans", "variant", "hovered", { size: "xs" }),
+ background: background(layer, "variant", "hovered"),
+ border: border(layer, "variant", "hovered"),
},
};
const avatarWidth = 18;
return {
- background: backgroundColor(theme, 300),
+ background: background(layer),
joiningProjectAvatar: {
cornerRadius: 40,
width: 80,
},
joiningProjectMessage: {
padding: 12,
- ...text(theme, "sans", "primary", { size: "lg" }),
+ ...text(layer, "sans", { size: "lg" }),
},
externalLocationMessage: {
- background: backgroundColor(theme, "on500Info"),
- border: border(theme, "secondary"),
+ background: background(colorScheme.middle, "accent"),
+ border: border(colorScheme.middle, "accent"),
cornerRadius: 6,
padding: 12,
margin: { bottom: 8, right: 8 },
- ...text(theme, "sans", "secondary", { size: "xs" }),
+ ...text(colorScheme.middle, "sans", "accent", { size: "xs" }),
},
leaderBorderOpacity: 0.7,
leaderBorderWidth: 2.0,
- tabBar: tabBar(theme),
+ tabBar: tabBar(colorScheme),
modal: {
margin: {
bottom: 52,
@@ -68,28 +62,28 @@ export default function workspace(theme: Theme) {
},
sidebar: {
initialSize: 240,
- border: {
- color: border(theme, "primary").color,
- width: 1,
- left: true,
- right: true,
- }
+ border: border(layer, { left: true, right: true }),
},
paneDivider: {
- color: border(theme, "secondary").color,
+ color: borderColor(layer),
width: 1,
},
- statusBar: statusBar(theme),
+ statusBar: statusBar(colorScheme),
titlebar: {
avatarWidth,
avatarMargin: 8,
- height: 33,
- background: backgroundColor(theme, 100),
+ height: 33, // 32px + 1px for overlaid border
+ background: background(layer),
+ border: border(layer, { bottom: true, overlay: true }),
padding: {
left: 80,
right: titlebarPadding,
},
- title: text(theme, "sans", "primary"),
+
+ // Project
+ title: text(layer, "sans", "variant"),
+
+ // Collaborators
avatar: {
cornerRadius: avatarWidth / 2,
border: {
@@ -108,15 +102,18 @@ export default function workspace(theme: Theme) {
avatarRibbon: {
height: 3,
width: 12,
- // TODO: The background for this ideally should be
- // set with a token, not hardcoded in rust
+ // TODO: Chore: Make avatarRibbon colors driven by the theme rather than being hard coded.
},
- border: border(theme, "primary", { bottom: true, overlay: true }),
+
+ // Sign in buttom
+ // FlatButton, Variant
signInPrompt: {
...titlebarButton
},
+
+ // Offline Indicator
offlineIcon: {
- color: iconColor(theme, "secondary"),
+ color: foreground(layer, "variant"),
width: 16,
margin: {
left: titlebarPadding,
@@ -125,90 +122,93 @@ export default function workspace(theme: Theme) {
right: 4,
},
},
+
+ // Notice that the collaboration server is out of date
outdatedWarning: {
- ...text(theme, "sans", "warning", { size: "xs" }),
- background: backgroundColor(theme, "warning"),
- border: border(theme, "warning"),
+ ...text(layer, "sans", "warning", { size: "xs" }),
+ background: withOpacity(background(layer, "warning"), 0.3),
+ border: border(layer, "warning"),
margin: {
left: titlebarPadding,
},
padding: {
- left: 6,
- right: 6,
+ left: 8,
+ right: 8,
},
cornerRadius: 6,
},
callControl: {
cornerRadius: 6,
- color: iconColor(theme, "secondary"),
+ color: foreground(layer, "variant"),
iconWidth: 12,
buttonWidth: 20,
hover: {
- background: backgroundColor(theme, "on300", "hovered"),
- color: iconColor(theme, "active"),
+ background: background(layer, "variant", "hovered"),
+ color: foreground(layer, "variant", "hovered"),
},
},
toggleContactsButton: {
margin: { left: 6 },
cornerRadius: 6,
- color: iconColor(theme, "secondary"),
+ color: foreground(layer, "variant"),
iconWidth: 8,
buttonWidth: 20,
active: {
- background: backgroundColor(theme, "on300", "active"),
- color: iconColor(theme, "active"),
+ background: background(layer, "variant", "active"),
+ color: foreground(layer, "variant", "active"),
},
hover: {
- background: backgroundColor(theme, "on300", "hovered"),
- color: iconColor(theme, "active"),
+ background: background(layer, "variant", "hovered"),
+ color: foreground(layer, "variant", "hovered"),
},
},
toggleContactsBadge: {
cornerRadius: 3,
padding: 2,
margin: { top: 3, left: 3 },
- border: { width: 1, color: workspaceBackground(theme) },
- background: iconColor(theme, "feature"),
+ border: border(layer),
+ background: foreground(layer, "accent"),
},
shareButton: {
...titlebarButton
}
},
+
toolbar: {
height: 34,
- background: backgroundColor(theme, 500),
- border: border(theme, "secondary", { bottom: true }),
+ background: background(colorScheme.highest),
+ border: border(colorScheme.highest, { bottom: true }),
itemSpacing: 8,
navButton: {
- color: iconColor(theme, "primary"),
+ color: foreground(colorScheme.highest, "on"),
iconWidth: 12,
buttonWidth: 24,
cornerRadius: 6,
hover: {
- color: iconColor(theme, "active"),
- background: backgroundColor(theme, "on500", "hovered"),
+ color: foreground(colorScheme.highest, "on", "hovered"),
+ background: background(colorScheme.highest, "on", "hovered"),
},
disabled: {
- color: withOpacity(iconColor(theme, "muted"), 0.6),
+ color: foreground(colorScheme.highest, "on", "disabled"),
},
},
padding: { left: 8, right: 8, top: 4, bottom: 4 },
},
breadcrumbs: {
- ...text(theme, "mono", "secondary"),
+ ...text(layer, "mono", "variant"),
padding: { left: 6 },
},
disconnectedOverlay: {
- ...text(theme, "sans", "active"),
- background: withOpacity(theme.backgroundColor[500].base, 0.8),
+ ...text(layer, "sans"),
+ background: withOpacity(background(layer), 0.8),
},
notification: {
margin: { top: 10 },
- background: backgroundColor(theme, 300),
+ background: background(colorScheme.middle),
cornerRadius: 6,
padding: 12,
- border: border(theme, "primary"),
- shadow: modalShadow(theme),
+ border: border(colorScheme.middle),
+ shadow: colorScheme.popoverShadow,
},
notifications: {
width: 400,
@@ -217,18 +217,15 @@ export default function workspace(theme: Theme) {
dock: {
initialSizeRight: 640,
initialSizeBottom: 480,
- wash_color: withOpacity(theme.backgroundColor[500].base, 0.5),
+ wash_color: withOpacity(background(colorScheme.highest), 0.5),
panel: {
- border: {
- ...border(theme, "secondary"),
- width: 1
- },
+ border: border(colorScheme.highest),
},
maximized: {
- margin: 24,
- border: border(theme, "secondary", { "overlay": true }),
- shadow: modalShadow(theme),
- }
- }
+ margin: 32,
+ border: border(colorScheme.highest, { overlay: true }),
+ 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 { colorRamp, createTheme } from "./common/base16";
+import { colorRamp, createColorScheme } from "./common/ramps";
const name = "andromeda";
const ramps = {
- neutral: chroma.scale([
- "#1E2025",
- "#23262E",
- "#292E38",
- "#2E323C",
- "#ACA8AE",
- "#CBC9CF",
- "#E1DDE4",
- "#F7F7F8",
- ]),
+ neutral: chroma
+ .scale([
+ "#1E2025",
+ "#23262E",
+ "#292E38",
+ "#2E323C",
+ "#ACA8AE",
+ "#CBC9CF",
+ "#E1DDE4",
+ "#F7F7F8",
+ ])
+ .domain([0, 0.15, 0.25, 0.35, 0.7, 0.8, 0.9, 1]),
red: colorRamp(chroma("#F92672")),
orange: colorRamp(chroma("#F39C12")),
yellow: colorRamp(chroma("#FFE66D")),
@@ -24,4 +26,4 @@ const ramps = {
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 { colorRamp, createTheme } from "./common/base16";
+import { colorRamp, createColorScheme } from "./common/ramps";
const name = "cave";
-const ramps = {
- neutral: chroma.scale([
- "#19171c",
- "#26232a",
- "#585260",
- "#655f6d",
- "#7e7887",
- "#8b8792",
- "#e2dfe7",
- "#efecf4",
- ]),
+export const dark = createColorScheme(`${name}-dark`, false, {
+ neutral: chroma
+ .scale([
+ "#19171c",
+ "#26232a",
+ "#585260",
+ "#655f6d",
+ "#7e7887",
+ "#8b8792",
+ "#e2dfe7",
+ "#efecf4",
+ ])
+ .domain([0, 0.15, 0.45, 0.6, 0.65, 0.7, 0.85, 1]),
red: colorRamp(chroma("#be4678")),
orange: colorRamp(chroma("#aa573c")),
yellow: colorRamp(chroma("#a06e3b")),
@@ -22,7 +24,26 @@ const ramps = {
blue: colorRamp(chroma("#576ddb")),
violet: colorRamp(chroma("#955ae7")),
magenta: colorRamp(chroma("#bf40bf")),
-};
+});
-export const dark = createTheme(`${name}-dark`, false, ramps);
-export const light = createTheme(`${name}-light`, true, ramps);
+export const light = createColorScheme(`${name}-light`, true, {
+ 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")),
+});
@@ -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;
+}
@@ -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,
+ },
+ };
+}
@@ -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 { colorRamp, createTheme } from "./common/base16";
+import { colorRamp, createColorScheme } from "./common/ramps";
const name = "one";
const author = "Chris Kempson (http://chriskempson.com)";
@@ -24,16 +24,9 @@ const base0E = "#c678dd";
const base0F = "#be5046";
const ramps = {
- neutral: chroma.scale([
- base00,
- base01,
- base02,
- base03,
- base04,
- base05,
- base06,
- base07,
- ]),
+ neutral: chroma
+ .scale([base00, base01, base02, base03, base04, base05, base06, base07])
+ .domain([0.05, 0.22, 0.25, 0.45, 0.62, 0.8, 0.9, 1]),
red: colorRamp(chroma(base08)),
orange: colorRamp(chroma(base09)),
yellow: colorRamp(chroma(base0A)),
@@ -44,4 +37,4 @@ const ramps = {
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 { colorRamp, createTheme } from "./common/base16";
+import { colorRamp, createColorScheme } from "./common/ramps";
const name = "one";
const author = "Daniel Pfeifer (http://github.com/purpleKarrot)";
@@ -24,16 +24,9 @@ const base0E = "#a626a4";
const base0F = "#986801";
const ramps = {
- neutral: chroma.scale([
- base00,
- base01,
- base02,
- base03,
- base04,
- base05,
- base06,
- base07,
- ]),
+ neutral: chroma
+ .scale([base00, base01, base02, base03, base04, base05, base06, base07])
+ .domain([0, 0.05, 0.77, 1]),
red: colorRamp(chroma(base08)),
orange: colorRamp(chroma(base09)),
yellow: colorRamp(chroma(base0A)),
@@ -44,4 +37,4 @@ const ramps = {
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 { colorRamp, createTheme } from "./common/base16";
+import { colorRamp, createColorScheme } from "./common/ramps";
const name = "rosΓ©-pine-dawn";
const ramps = {
- neutral: chroma.scale([
- "#575279",
- "#797593",
- "#9893A5",
- "#B5AFB8",
- "#D3CCCC",
- "#F2E9E1",
- "#FFFAF3",
- "#FAF4ED",
- ]),
+ neutral: chroma
+ .scale([
+ "#575279",
+ "#797593",
+ "#9893A5",
+ "#B5AFB8",
+ "#D3CCCC",
+ "#F2E9E1",
+ "#FFFAF3",
+ "#FAF4ED",
+ ])
+ .domain([0, 0.35, 0.45, 0.65, 0.7, 0.8, 0.9, 1]),
red: colorRamp(chroma("#B4637A")),
orange: colorRamp(chroma("#D7827E")),
yellow: colorRamp(chroma("#EA9D34")),
@@ -24,4 +26,4 @@ const ramps = {
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 { colorRamp, createTheme } from "./common/base16";
+import { colorRamp, createColorScheme } from "./common/ramps";
const name = "rosΓ©-pine-moon";
const ramps = {
- neutral: chroma.scale([
- "#232136",
- "#2A273F",
- "#393552",
- "#3E3A53",
- "#56526C",
- "#6E6A86",
- "#908CAA",
- "#E0DEF4",
- ]),
+ neutral: chroma
+ .scale([
+ "#232136",
+ "#2A273F",
+ "#393552",
+ "#3E3A53",
+ "#56526C",
+ "#6E6A86",
+ "#908CAA",
+ "#E0DEF4",
+ ])
+ .domain([0, 0.3, 0.55, 1]),
red: colorRamp(chroma("#EB6F92")),
orange: colorRamp(chroma("#EBBCBA")),
yellow: colorRamp(chroma("#F6C177")),
@@ -24,4 +26,4 @@ const ramps = {
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 { colorRamp, createTheme } from "./common/base16";
+import { colorRamp, createColorScheme } from "./common/ramps";
const name = "rosΓ©-pine";
@@ -24,4 +24,4 @@ const ramps = {
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 { colorRamp, createTheme } from "./common/base16";
+import { colorRamp, createColorScheme } from "./common/ramps";
const name = "sandcastle";
@@ -17,11 +17,11 @@ const ramps = {
red: colorRamp(chroma("#B4637A")),
orange: colorRamp(chroma("#a07e3b")),
yellow: colorRamp(chroma("#a07e3b")),
- green: colorRamp(chroma("#528b8b")),
+ green: colorRamp(chroma("#83a598")),
cyan: colorRamp(chroma("#83a598")),
- blue: colorRamp(chroma("#83a598")),
+ blue: colorRamp(chroma("#528b8b")),
violet: colorRamp(chroma("#d75f5f")),
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 { colorRamp, createTheme } from "./common/base16";
+import { colorRamp, createColorScheme } from "./common/ramps";
const name = "solarized";
const ramps = {
- neutral: chroma.scale([
- "#002b36",
- "#073642",
- "#586e75",
- "#657b83",
- "#839496",
- "#93a1a1",
- "#eee8d5",
- "#fdf6e3",
- ]),
+ neutral: chroma
+ .scale([
+ "#002b36",
+ "#073642",
+ "#586e75",
+ "#657b83",
+ "#839496",
+ "#93a1a1",
+ "#eee8d5",
+ "#fdf6e3",
+ ])
+ .domain([0, 0.2, 0.38, 0.45, 0.65, 0.7, 0.85, 1]),
red: colorRamp(chroma("#dc322f")),
orange: colorRamp(chroma("#cb4b16")),
yellow: colorRamp(chroma("#b58900")),
@@ -24,5 +26,5 @@ const ramps = {
magenta: colorRamp(chroma("#d33682")),
};
-export const dark = createTheme(`${name}-dark`, false, ramps);
-export const light = createTheme(`${name}-light`, true, ramps);
+export const dark = createColorScheme(`${name}-dark`, false, ramps);
+export const light = createColorScheme(`${name}-light`, true, ramps);
@@ -1,19 +1,21 @@
import chroma from "chroma-js";
-import { colorRamp, createTheme } from "./common/base16";
+import { colorRamp, createColorScheme } from "./common/ramps";
const name = "sulphurpool";
const ramps = {
- neutral: chroma.scale([
- "#202746",
- "#293256",
- "#5e6687",
- "#6b7394",
- "#898ea4",
- "#979db4",
- "#dfe2f1",
- "#f5f7ff",
- ]),
+ neutral: chroma
+ .scale([
+ "#202746",
+ "#293256",
+ "#5e6687",
+ "#6b7394",
+ "#898ea4",
+ "#979db4",
+ "#dfe2f1",
+ "#f5f7ff",
+ ])
+ .domain([0, 0.2, 0.38, 0.45, 0.65, 0.7, 0.85, 1]),
red: colorRamp(chroma("#c94922")),
orange: colorRamp(chroma("#c76b29")),
yellow: colorRamp(chroma("#c08b30")),
@@ -24,5 +26,5 @@ const ramps = {
magenta: colorRamp(chroma("#9c637a")),
};
-export const dark = createTheme(`${name}-dark`, false, ramps);
-export const light = createTheme(`${name}-light`, true, ramps);
+export const dark = createColorScheme(`${name}-dark`, false, ramps);
+export const light = createColorScheme(`${name}-light`, true, ramps);
@@ -1,19 +1,21 @@
import chroma from "chroma-js";
-import { colorRamp, createTheme } from "./common/base16";
+import { colorRamp, createColorScheme } from "./common/ramps";
const name = "summercamp";
const ramps = {
- neutral: chroma.scale([
- "#1c1810",
- "#2a261c",
- "#3a3527",
- "#3a3527",
- "#5f5b45",
- "#736e55",
- "#bab696",
- "#f8f5de",
- ]),
+ neutral: chroma
+ .scale([
+ "#1c1810",
+ "#2a261c",
+ "#3a3527",
+ "#3a3527",
+ "#5f5b45",
+ "#736e55",
+ "#bab696",
+ "#f8f5de",
+ ])
+ .domain([0, 0.2, 0.38, 0.4, 0.65, 0.7, 0.85, 1]),
red: colorRamp(chroma("#e35142")),
orange: colorRamp(chroma("#fba11b")),
yellow: colorRamp(chroma("#f2ff27")),
@@ -24,4 +26,4 @@ const ramps = {
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 { colorRamp, createTheme } from "./common/base16";
+import { colorRamp, createColorScheme } from "./common/ramps";
/**
* Theme Name
@@ -56,14 +56,14 @@ const ramps = {
};
/**
- * Theme Variants
+ * Color Scheme Variants
*
* Currently we only support (and require) dark and light themes
* Eventually you will be able to have only a light or dark theme,
* and define other variants here.
*
- * createTheme([name], [isLight], [arrayOfRamps])
+ * createColorScheme([name], [isLight], [arrayOfRamps])
**/
-export const dark = createTheme(`${name}-dark`, false, ramps);
-export const light = createTheme(`${name}-light`, true, ramps);
+export const dark = createColorScheme(`${name}-dark`, false, ramps);
+export const light = createColorScheme(`${name}-light`, true, ramps);