diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index baa5da9bf719965de8e93a48534cfcc31f283f45..a32f25fbe9bf0b96585b5e4242daff92216a34a2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,9 @@ env: jobs: rustfmt: name: Check formatting - runs-on: self-hosted + runs-on: + - self-hosted + - test steps: - name: Install Rust run: | diff --git a/Cargo.lock b/Cargo.lock index e8410b25f045c09b6226df2bd4be4a22f8559d73..615825bd757f5897d6c11970307ffe77799c39eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -794,7 +794,7 @@ dependencies = [ [[package]] name = "bromberg_sl2" version = "0.6.0" -source = "git+https://github.com/zed-industries/bromberg_sl2?rev=dac565a90e8f9245f48ff46225c915dc50f76920#dac565a90e8f9245f48ff46225c915dc50f76920" +source = "git+https://github.com/zed-industries/bromberg_sl2?rev=950bc5482c216c395049ae33ae4501e08975f17f#950bc5482c216c395049ae33ae4501e08975f17f" dependencies = [ "digest 0.9.0", "lazy_static", @@ -1188,7 +1188,7 @@ dependencies = [ [[package]] name = "collab" -version = "0.5.4" +version = "0.6.1" dependencies = [ "anyhow", "async-tungstenite", @@ -1257,6 +1257,7 @@ dependencies = [ "client", "clock", "collections", + "context_menu", "editor", "futures 0.3.25", "fuzzy", @@ -8356,7 +8357,7 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] name = "zed" -version = "0.75.0" +version = "0.76.0" dependencies = [ "activity_indicator", "anyhow", diff --git a/assets/icons/ellipsis_14.svg b/assets/icons/ellipsis_14.svg new file mode 100644 index 0000000000000000000000000000000000000000..5d45af2b6f249f103ae2f1f3e8df48905f2fd832 --- /dev/null +++ b/assets/icons/ellipsis_14.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/leave_12.svg b/assets/icons/leave_12.svg new file mode 100644 index 0000000000000000000000000000000000000000..84491384b8cc7f80d4a727e75c142ee509b451ac --- /dev/null +++ b/assets/icons/leave_12.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index e8f055cb7d740fc1ce15e88d496dce60a4b0ea6b..cce65eda8abcadcb632d1b496c10b2dc283dc548 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -228,6 +228,7 @@ "replace_newest": true } ], + "cmd-k cmd-i": "editor::Hover", "cmd-/": [ "editor::ToggleComments", { @@ -418,7 +419,7 @@ { "bindings": { "ctrl-alt-cmd-f": "workspace::FollowNextCollaborator", - "cmd-shift-c": "collab::ToggleCollaborationMenu", + "cmd-shift-c": "collab::ToggleContactsMenu", "cmd-alt-i": "zed::DebugElements" } }, @@ -456,7 +457,7 @@ } }, { - "context": "Dock", + "context": "Pane && docked", "bindings": { "shift-escape": "dock::HideDock", "cmd-escape": "dock::RemoveTabFromDock" diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 824fb63c0f35969efeda1bb9cb2ded01e386d539..a24c4aff6900b14b939767b4b5caca6abb9c4486 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -27,6 +27,7 @@ "h": "vim::Left", "backspace": "vim::Backspace", "j": "vim::Down", + "enter": "vim::NextLineStart", "k": "vim::Up", "l": "vim::Right", "$": "vim::EndOfLine", diff --git a/assets/settings/default.json b/assets/settings/default.json index f6fb61d65c83e0352dc43455d10627708b2e35b9..00866af2caf86817e686d69677ab3225aceb09ff 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -83,7 +83,7 @@ "hard_tabs": false, // How many columns a tab should occupy. "tab_size": 4, - // Control what info Zed sends to our servers + // Control what info is collected by Zed. "telemetry": { // Send debug info like crash reports. "diagnostics": true, diff --git a/crates/activity_indicator/src/activity_indicator.rs b/crates/activity_indicator/src/activity_indicator.rs index f3a6f7328ad34ea0e6cffc30fed3742937b2275a..2041bbc793f75f634582fa5bdbb8c03044d95b68 100644 --- a/crates/activity_indicator/src/activity_indicator.rs +++ b/crates/activity_indicator/src/activity_indicator.rs @@ -33,6 +33,19 @@ struct LspStatus { status: LanguageServerBinaryStatus, } +struct PendingWork<'a> { + language_server_name: &'a str, + progress_token: &'a str, + progress: &'a LanguageServerProgress, +} + +#[derive(Default)] +struct Content { + icon: Option<&'static str>, + message: String, + action: Option>, +} + pub fn init(cx: &mut MutableAppContext) { cx.add_action(ActivityIndicator::show_error_message); cx.add_action(ActivityIndicator::dismiss_error_message); @@ -69,6 +82,8 @@ impl ActivityIndicator { if let Some(auto_updater) = auto_updater.as_ref() { cx.observe(auto_updater, |_, _, cx| cx.notify()).detach(); } + cx.observe_active_labeled_tasks(|_, cx| cx.notify()) + .detach(); Self { statuses: Default::default(), @@ -130,7 +145,7 @@ impl ActivityIndicator { fn pending_language_server_work<'a>( &self, cx: &'a AppContext, - ) -> impl Iterator { + ) -> impl Iterator> { self.project .read(cx) .language_server_statuses() @@ -142,23 +157,29 @@ impl ActivityIndicator { let mut pending_work = status .pending_work .iter() - .map(|(token, progress)| (status.name.as_str(), token.as_str(), progress)) + .map(|(token, progress)| PendingWork { + language_server_name: status.name.as_str(), + progress_token: token.as_str(), + progress, + }) .collect::>(); - pending_work.sort_by_key(|(_, _, progress)| Reverse(progress.last_update_at)); + pending_work.sort_by_key(|work| Reverse(work.progress.last_update_at)); Some(pending_work) } }) .flatten() } - fn content_to_render( - &mut self, - cx: &mut RenderContext, - ) -> (Option<&'static str>, String, Option>) { + fn content_to_render(&mut self, cx: &mut RenderContext) -> Content { // Show any language server has pending activity. let mut pending_work = self.pending_language_server_work(cx); - if let Some((lang_server_name, progress_token, progress)) = pending_work.next() { - let mut message = lang_server_name.to_string(); + if let Some(PendingWork { + language_server_name, + progress_token, + progress, + }) = pending_work.next() + { + let mut message = language_server_name.to_string(); message.push_str(": "); if let Some(progress_message) = progress.message.as_ref() { @@ -176,7 +197,11 @@ impl ActivityIndicator { write!(&mut message, " + {} more", additional_work_count).unwrap(); } - return (None, message, None); + return Content { + icon: None, + message, + action: None, + }; } // Show any language server installation info. @@ -199,19 +224,19 @@ impl ActivityIndicator { } if !downloading.is_empty() { - return ( - Some(DOWNLOAD_ICON), - format!( + return Content { + icon: Some(DOWNLOAD_ICON), + message: format!( "Downloading {} language server{}...", downloading.join(", "), if downloading.len() > 1 { "s" } else { "" } ), - None, - ); + action: None, + }; } else if !checking_for_update.is_empty() { - return ( - Some(DOWNLOAD_ICON), - format!( + return Content { + icon: Some(DOWNLOAD_ICON), + message: format!( "Checking for updates to {} language server{}...", checking_for_update.join(", "), if checking_for_update.len() > 1 { @@ -220,53 +245,61 @@ impl ActivityIndicator { "" } ), - None, - ); + action: None, + }; } else if !failed.is_empty() { - return ( - Some(WARNING_ICON), - format!( + return Content { + icon: Some(WARNING_ICON), + message: format!( "Failed to download {} language server{}. Click to show error.", failed.join(", "), if failed.len() > 1 { "s" } else { "" } ), - Some(Box::new(ShowErrorMessage)), - ); + action: Some(Box::new(ShowErrorMessage)), + }; } // Show any application auto-update info. if let Some(updater) = &self.auto_updater { - match &updater.read(cx).status() { - AutoUpdateStatus::Checking => ( - Some(DOWNLOAD_ICON), - "Checking for Zed updates…".to_string(), - None, - ), - AutoUpdateStatus::Downloading => ( - Some(DOWNLOAD_ICON), - "Downloading Zed update…".to_string(), - None, - ), - AutoUpdateStatus::Installing => ( - Some(DOWNLOAD_ICON), - "Installing Zed update…".to_string(), - None, - ), - AutoUpdateStatus::Updated => ( - None, - "Click to restart and update Zed".to_string(), - Some(Box::new(workspace::Restart)), - ), - AutoUpdateStatus::Errored => ( - Some(WARNING_ICON), - "Auto update failed".to_string(), - Some(Box::new(DismissErrorMessage)), - ), + return match &updater.read(cx).status() { + AutoUpdateStatus::Checking => Content { + icon: Some(DOWNLOAD_ICON), + message: "Checking for Zed updates…".to_string(), + action: None, + }, + AutoUpdateStatus::Downloading => Content { + icon: Some(DOWNLOAD_ICON), + message: "Downloading Zed update…".to_string(), + action: None, + }, + AutoUpdateStatus::Installing => Content { + icon: Some(DOWNLOAD_ICON), + message: "Installing Zed update…".to_string(), + action: None, + }, + AutoUpdateStatus::Updated => Content { + icon: None, + message: "Click to restart and update Zed".to_string(), + action: Some(Box::new(workspace::Restart)), + }, + AutoUpdateStatus::Errored => Content { + icon: Some(WARNING_ICON), + message: "Auto update failed".to_string(), + action: Some(Box::new(DismissErrorMessage)), + }, AutoUpdateStatus::Idle => Default::default(), - } - } else { - Default::default() + }; } + + if let Some(most_recent_active_task) = cx.active_labeled_tasks().last() { + return Content { + icon: None, + message: most_recent_active_task.to_string(), + action: None, + }; + } + + Default::default() } } @@ -280,7 +313,11 @@ impl View for ActivityIndicator { } fn render(&mut self, cx: &mut RenderContext) -> ElementBox { - let (icon, message, action) = self.content_to_render(cx); + let Content { + icon, + message, + action, + } = self.content_to_render(cx); let mut element = MouseEventHandler::::new(0, cx, |state, cx| { let theme = &cx diff --git a/crates/call/src/call.rs b/crates/call/src/call.rs index 64584e61400b414620ba640f2fbc0b79825c535e..dfe4f39e0e85bef14a52a22a3c8c0f1b9bfa84b6 100644 --- a/crates/call/src/call.rs +++ b/crates/call/src/call.rs @@ -284,6 +284,18 @@ impl ActiveCall { } } + pub fn unshare_project( + &mut self, + project: ModelHandle, + cx: &mut ModelContext, + ) -> Result<()> { + if let Some((room, _)) = self.room.as_ref() { + room.update(cx, |room, cx| room.unshare_project(project, cx)) + } else { + Err(anyhow!("no active call")) + } + } + pub fn set_location( &mut self, project: Option<&ModelHandle>, diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 7527a693269725f0a6eae3bea48790e7c79dfb00..06680c1dcb86335c2e120df3043fd413d3f26af1 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -55,6 +55,7 @@ pub struct Room { leave_when_empty: bool, client: Arc, user_store: ModelHandle, + follows_by_leader_id_project_id: HashMap<(PeerId, u64), Vec>, subscriptions: Vec, pending_room_update: Option>, maintain_connection: Option>>, @@ -148,6 +149,7 @@ impl Room { pending_room_update: None, client, user_store, + follows_by_leader_id_project_id: Default::default(), maintain_connection: Some(maintain_connection), } } @@ -275,14 +277,12 @@ impl Room { ) -> Result<()> { let mut client_status = client.status(); loop { - let is_connected = client_status - .next() - .await - .map_or(false, |s| s.is_connected()); - + let _ = client_status.try_recv(); + let is_connected = client_status.borrow().is_connected(); // Even if we're initially connected, any future change of the status means we momentarily disconnected. if !is_connected || client_status.next().await.is_some() { log::info!("detected client disconnection"); + this.upgrade(&cx) .ok_or_else(|| anyhow!("room was dropped"))? .update(&mut cx, |this, cx| { @@ -296,12 +296,7 @@ impl Room { let client_reconnection = async { let mut remaining_attempts = 3; while remaining_attempts > 0 { - log::info!( - "waiting for client status change, remaining attempts {}", - remaining_attempts - ); - let Some(status) = client_status.next().await else { break }; - if status.is_connected() { + if client_status.borrow().is_connected() { log::info!("client reconnected, attempting to rejoin room"); let Some(this) = this.upgrade(&cx) else { break }; @@ -315,7 +310,15 @@ impl Room { } else { remaining_attempts -= 1; } + } else if client_status.borrow().is_signed_out() { + return false; } + + log::info!( + "waiting for client status change, remaining attempts {}", + remaining_attempts + ); + client_status.next().await; } false } @@ -337,18 +340,20 @@ impl Room { } } - // The client failed to re-establish a connection to the server - // or an error occurred while trying to re-join the room. Either way - // we leave the room and return an error. - if let Some(this) = this.upgrade(&cx) { - log::info!("reconnection failed, leaving room"); - let _ = this.update(&mut cx, |this, cx| this.leave(cx)); - } - return Err(anyhow!( - "can't reconnect to room: client failed to re-establish connection" - )); + break; } } + + // The client failed to re-establish a connection to the server + // or an error occurred while trying to re-join the room. Either way + // we leave the room and return an error. + if let Some(this) = this.upgrade(&cx) { + log::info!("reconnection failed, leaving room"); + let _ = this.update(&mut cx, |this, cx| this.leave(cx)); + } + Err(anyhow!( + "can't reconnect to room: client failed to re-establish connection" + )) } fn rejoin(&mut self, cx: &mut ModelContext) -> Task> { @@ -457,6 +462,12 @@ impl Room { self.participant_user_ids.contains(&user_id) } + pub fn followers_for(&self, leader_id: PeerId, project_id: u64) -> &[PeerId] { + self.follows_by_leader_id_project_id + .get(&(leader_id, project_id)) + .map_or(&[], |v| v.as_slice()) + } + async fn handle_room_updated( this: ModelHandle, envelope: TypedEnvelope, @@ -487,11 +498,13 @@ impl Room { .iter() .map(|p| p.user_id) .collect::>(); + let remote_participant_user_ids = room .participants .iter() .map(|p| p.user_id) .collect::>(); + let (remote_participants, pending_participants) = self.user_store.update(cx, move |user_store, cx| { ( @@ -499,6 +512,7 @@ impl Room { user_store.get_users(pending_participant_user_ids, cx), ) }); + self.pending_room_update = Some(cx.spawn(|this, mut cx| async move { let (remote_participants, pending_participants) = futures::join!(remote_participants, pending_participants); @@ -620,6 +634,27 @@ impl Room { } } + this.follows_by_leader_id_project_id.clear(); + for follower in room.followers { + let project_id = follower.project_id; + let (leader, follower) = match (follower.leader_id, follower.follower_id) { + (Some(leader), Some(follower)) => (leader, follower), + + _ => { + log::error!("Follower message {follower:?} missing some state"); + continue; + } + }; + + let list = this + .follows_by_leader_id_project_id + .entry((leader, project_id)) + .or_insert(Vec::new()); + if !list.contains(&follower) { + list.push(follower); + } + } + this.pending_room_update.take(); if this.should_leave() { log::info!("room is empty, leaving"); @@ -793,6 +828,20 @@ impl Room { }) } + pub(crate) fn unshare_project( + &mut self, + project: ModelHandle, + cx: &mut ModelContext, + ) -> Result<()> { + let project_id = match project.read(cx).remote_id() { + Some(project_id) => project_id, + None => return Ok(()), + }; + + self.client.send(proto::UnshareProject { project_id })?; + project.update(cx, |this, cx| this.unshare(cx)) + } + pub(crate) fn set_location( &mut self, project: Option<&ModelHandle>, diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index eba58304d7c89c8de749aee932034cb8079928eb..f36fa67d9d6790be5b124200ebd46f0ebc35d0cf 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -66,7 +66,7 @@ pub const ZED_SECRET_CLIENT_TOKEN: &str = "618033988749894"; pub const INITIAL_RECONNECTION_DELAY: Duration = Duration::from_millis(100); pub const CONNECTION_TIMEOUT: Duration = Duration::from_secs(5); -actions!(client, [Authenticate]); +actions!(client, [Authenticate, SignOut]); pub fn init(client: Arc, cx: &mut MutableAppContext) { cx.add_global_action({ @@ -79,6 +79,16 @@ pub fn init(client: Arc, cx: &mut MutableAppContext) { .detach(); } }); + cx.add_global_action({ + let client = client.clone(); + move |_: &SignOut, cx| { + let client = client.clone(); + cx.spawn(|cx| async move { + client.disconnect(&cx); + }) + .detach(); + } + }); } pub struct Client { @@ -169,6 +179,10 @@ impl Status { pub fn is_connected(&self) -> bool { matches!(self, Self::Connected { .. }) } + + pub fn is_signed_out(&self) -> bool { + matches!(self, Self::SignedOut | Self::UpgradeRequired) + } } struct ClientState { @@ -1152,11 +1166,9 @@ impl Client { }) } - pub fn disconnect(self: &Arc, cx: &AsyncAppContext) -> Result<()> { - let conn_id = self.connection_id()?; - self.peer.disconnect(conn_id); + pub fn disconnect(self: &Arc, cx: &AsyncAppContext) { + self.peer.teardown(); self.set_status(Status::SignedOut, cx); - Ok(()) } fn connection_id(&self) -> Result { diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index 9301a1974aad839a222c60b5be290d058bdfa378..86fe9174bf0bfec0aeb5b90daab8d7e09c898bd7 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] default-run = "collab" edition = "2021" name = "collab" -version = "0.5.4" +version = "0.6.1" publish = false [[bin]] diff --git a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql index 32254d5757da77f7b90f6c675b0a432418d32624..89b924087ef987c89ec58e65f2b165a7d11b4afa 100644 --- a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql +++ b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql @@ -143,3 +143,17 @@ CREATE TABLE "servers" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT, "environment" VARCHAR NOT NULL ); + +CREATE TABLE "followers" ( + "id" INTEGER PRIMARY KEY AUTOINCREMENT, + "room_id" INTEGER NOT NULL REFERENCES rooms (id) ON DELETE CASCADE, + "project_id" INTEGER NOT NULL REFERENCES projects (id) ON DELETE CASCADE, + "leader_connection_server_id" INTEGER NOT NULL REFERENCES servers (id) ON DELETE CASCADE, + "leader_connection_id" INTEGER NOT NULL, + "follower_connection_server_id" INTEGER NOT NULL REFERENCES servers (id) ON DELETE CASCADE, + "follower_connection_id" INTEGER NOT NULL +); +CREATE UNIQUE INDEX + "index_followers_on_project_id_and_leader_connection_server_id_and_leader_connection_id_and_follower_connection_server_id_and_follower_connection_id" +ON "followers" ("project_id", "leader_connection_server_id", "leader_connection_id", "follower_connection_server_id", "follower_connection_id"); +CREATE INDEX "index_followers_on_room_id" ON "followers" ("room_id"); diff --git a/crates/collab/migrations/20230202155735_followers.sql b/crates/collab/migrations/20230202155735_followers.sql new file mode 100644 index 0000000000000000000000000000000000000000..c82d6ba3bdaa4f2b2a60771bca7401c47678f247 --- /dev/null +++ b/crates/collab/migrations/20230202155735_followers.sql @@ -0,0 +1,15 @@ +CREATE TABLE IF NOT EXISTS "followers" ( + "id" SERIAL PRIMARY KEY, + "room_id" INTEGER NOT NULL REFERENCES rooms (id) ON DELETE CASCADE, + "project_id" INTEGER NOT NULL REFERENCES projects (id) ON DELETE CASCADE, + "leader_connection_server_id" INTEGER NOT NULL REFERENCES servers (id) ON DELETE CASCADE, + "leader_connection_id" INTEGER NOT NULL, + "follower_connection_server_id" INTEGER NOT NULL REFERENCES servers (id) ON DELETE CASCADE, + "follower_connection_id" INTEGER NOT NULL +); + +CREATE UNIQUE INDEX + "index_followers_on_project_id_and_leader_connection_server_id_and_leader_connection_id_and_follower_connection_server_id_and_follower_connection_id" +ON "followers" ("project_id", "leader_connection_server_id", "leader_connection_id", "follower_connection_server_id", "follower_connection_id"); + +CREATE INDEX "index_followers_on_room_id" ON "followers" ("room_id"); diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index af30073ab4ef424b8c9f84557cdd692cdfbfcf46..c4ff2e39188a1133cc086b00ab5194d242ef0cc8 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -1,5 +1,6 @@ mod access_token; mod contact; +mod follower; mod language_server; mod project; mod project_collaborator; @@ -157,7 +158,7 @@ impl Database { room_id: RoomId, new_server_id: ServerId, ) -> Result> { - self.room_transaction(|tx| async move { + self.room_transaction(room_id, |tx| async move { let stale_participant_filter = Condition::all() .add(room_participant::Column::RoomId.eq(room_id)) .add(room_participant::Column::AnsweringConnectionId.is_not_null()) @@ -190,17 +191,18 @@ impl Database { .filter(room_participant::Column::RoomId.eq(room_id)) .exec(&*tx) .await?; + project::Entity::delete_many() + .filter(project::Column::RoomId.eq(room_id)) + .exec(&*tx) + .await?; room::Entity::delete_by_id(room_id).exec(&*tx).await?; } - Ok(( - room_id, - RefreshedRoom { - room, - stale_participant_user_ids, - canceled_calls_to_user_ids, - }, - )) + Ok(RefreshedRoom { + room, + stale_participant_user_ids, + canceled_calls_to_user_ids, + }) }) .await } @@ -1129,18 +1131,16 @@ impl Database { user_id: UserId, connection: ConnectionId, live_kit_room: &str, - ) -> Result> { - self.room_transaction(|tx| async move { + ) -> Result { + self.transaction(|tx| async move { let room = room::ActiveModel { live_kit_room: ActiveValue::set(live_kit_room.into()), ..Default::default() } .insert(&*tx) .await?; - let room_id = room.id; - room_participant::ActiveModel { - room_id: ActiveValue::set(room_id), + room_id: ActiveValue::set(room.id), user_id: ActiveValue::set(user_id), answering_connection_id: ActiveValue::set(Some(connection.id as i32)), answering_connection_server_id: ActiveValue::set(Some(ServerId( @@ -1157,8 +1157,8 @@ impl Database { .insert(&*tx) .await?; - let room = self.get_room(room_id, &tx).await?; - Ok((room_id, room)) + let room = self.get_room(room.id, &tx).await?; + Ok(room) }) .await } @@ -1171,7 +1171,7 @@ impl Database { called_user_id: UserId, initial_project_id: Option, ) -> Result> { - self.room_transaction(|tx| async move { + self.room_transaction(room_id, |tx| async move { room_participant::ActiveModel { room_id: ActiveValue::set(room_id), user_id: ActiveValue::set(called_user_id), @@ -1190,7 +1190,7 @@ impl Database { let room = self.get_room(room_id, &tx).await?; let incoming_call = Self::build_incoming_call(&room, called_user_id) .ok_or_else(|| anyhow!("failed to build incoming call"))?; - Ok((room_id, (room, incoming_call))) + Ok((room, incoming_call)) }) .await } @@ -1200,7 +1200,7 @@ impl Database { room_id: RoomId, called_user_id: UserId, ) -> Result> { - self.room_transaction(|tx| async move { + self.room_transaction(room_id, |tx| async move { room_participant::Entity::delete_many() .filter( room_participant::Column::RoomId @@ -1210,7 +1210,7 @@ impl Database { .exec(&*tx) .await?; let room = self.get_room(room_id, &tx).await?; - Ok((room_id, room)) + Ok(room) }) .await } @@ -1257,7 +1257,7 @@ impl Database { calling_connection: ConnectionId, called_user_id: UserId, ) -> Result> { - self.room_transaction(|tx| async move { + self.room_transaction(room_id, |tx| async move { let participant = room_participant::Entity::find() .filter( Condition::all() @@ -1276,14 +1276,13 @@ impl Database { .one(&*tx) .await? .ok_or_else(|| anyhow!("no call to cancel"))?; - let room_id = participant.room_id; room_participant::Entity::delete(participant.into_active_model()) .exec(&*tx) .await?; let room = self.get_room(room_id, &tx).await?; - Ok((room_id, room)) + Ok(room) }) .await } @@ -1294,7 +1293,7 @@ impl Database { user_id: UserId, connection: ConnectionId, ) -> Result> { - self.room_transaction(|tx| async move { + self.room_transaction(room_id, |tx| async move { let result = room_participant::Entity::update_many() .filter( Condition::all() @@ -1316,7 +1315,7 @@ impl Database { Err(anyhow!("room does not exist or was already joined"))? } else { let room = self.get_room(room_id, &tx).await?; - Ok((room_id, room)) + Ok(room) } }) .await @@ -1328,9 +1327,9 @@ impl Database { user_id: UserId, connection: ConnectionId, ) -> Result> { - self.room_transaction(|tx| async { + let room_id = RoomId::from_proto(rejoin_room.id); + self.room_transaction(room_id, |tx| async { let tx = tx; - let room_id = RoomId::from_proto(rejoin_room.id); let participant_update = room_participant::Entity::update_many() .filter( Condition::all() @@ -1549,14 +1548,11 @@ impl Database { } let room = self.get_room(room_id, &tx).await?; - Ok(( - room_id, - RejoinedRoom { - room, - rejoined_projects, - reshared_projects, - }, - )) + Ok(RejoinedRoom { + room, + rejoined_projects, + reshared_projects, + }) }) .await } @@ -1717,13 +1713,78 @@ impl Database { .await } + pub async fn follow( + &self, + project_id: ProjectId, + leader_connection: ConnectionId, + follower_connection: ConnectionId, + ) -> Result> { + let room_id = self.room_id_for_project(project_id).await?; + self.room_transaction(room_id, |tx| async move { + follower::ActiveModel { + room_id: ActiveValue::set(room_id), + project_id: ActiveValue::set(project_id), + leader_connection_server_id: ActiveValue::set(ServerId( + leader_connection.owner_id as i32, + )), + leader_connection_id: ActiveValue::set(leader_connection.id as i32), + follower_connection_server_id: ActiveValue::set(ServerId( + follower_connection.owner_id as i32, + )), + follower_connection_id: ActiveValue::set(follower_connection.id as i32), + ..Default::default() + } + .insert(&*tx) + .await?; + + let room = self.get_room(room_id, &*tx).await?; + Ok(room) + }) + .await + } + + pub async fn unfollow( + &self, + project_id: ProjectId, + leader_connection: ConnectionId, + follower_connection: ConnectionId, + ) -> Result> { + let room_id = self.room_id_for_project(project_id).await?; + self.room_transaction(room_id, |tx| async move { + follower::Entity::delete_many() + .filter( + Condition::all() + .add(follower::Column::ProjectId.eq(project_id)) + .add( + follower::Column::LeaderConnectionServerId + .eq(leader_connection.owner_id) + .and(follower::Column::LeaderConnectionId.eq(leader_connection.id)), + ) + .add( + follower::Column::FollowerConnectionServerId + .eq(follower_connection.owner_id) + .and( + follower::Column::FollowerConnectionId + .eq(follower_connection.id), + ), + ), + ) + .exec(&*tx) + .await?; + + let room = self.get_room(room_id, &*tx).await?; + Ok(room) + }) + .await + } + pub async fn update_room_participant_location( &self, room_id: RoomId, connection: ConnectionId, location: proto::ParticipantLocation, ) -> Result> { - self.room_transaction(|tx| async { + self.room_transaction(room_id, |tx| async { let tx = tx; let location_kind; let location_project_id; @@ -1769,7 +1830,7 @@ impl Database { if result.rows_affected == 1 { let room = self.get_room(room_id, &tx).await?; - Ok((room_id, room)) + Ok(room) } else { Err(anyhow!("could not update room participant location"))? } @@ -1926,12 +1987,25 @@ impl Database { } } } + drop(db_projects); + + let mut db_followers = db_room.find_related(follower::Entity).stream(tx).await?; + let mut followers = Vec::new(); + while let Some(db_follower) = db_followers.next().await { + let db_follower = db_follower?; + followers.push(proto::Follower { + leader_id: Some(db_follower.leader_connection().into()), + follower_id: Some(db_follower.follower_connection().into()), + project_id: db_follower.project_id.to_proto(), + }); + } Ok(proto::Room { id: db_room.id.to_proto(), live_kit_room: db_room.live_kit_room, participants: participants.into_values().collect(), pending_participants, + followers, }) } @@ -1963,7 +2037,7 @@ impl Database { connection: ConnectionId, worktrees: &[proto::WorktreeMetadata], ) -> Result> { - self.room_transaction(|tx| async move { + self.room_transaction(room_id, |tx| async move { let participant = room_participant::Entity::find() .filter( Condition::all() @@ -2024,7 +2098,7 @@ impl Database { .await?; let room = self.get_room(room_id, &tx).await?; - Ok((room_id, (project.id, room))) + Ok((project.id, room)) }) .await } @@ -2034,7 +2108,8 @@ impl Database { project_id: ProjectId, connection: ConnectionId, ) -> Result)>> { - self.room_transaction(|tx| async move { + let room_id = self.room_id_for_project(project_id).await?; + self.room_transaction(room_id, |tx| async move { let guest_connection_ids = self.project_guest_connection_ids(project_id, &tx).await?; let project = project::Entity::find_by_id(project_id) @@ -2042,12 +2117,11 @@ impl Database { .await? .ok_or_else(|| anyhow!("project not found"))?; if project.host_connection()? == connection { - let room_id = project.room_id; project::Entity::delete(project.into_active_model()) .exec(&*tx) .await?; let room = self.get_room(room_id, &tx).await?; - Ok((room_id, (room, guest_connection_ids))) + Ok((room, guest_connection_ids)) } else { Err(anyhow!("cannot unshare a project hosted by another user"))? } @@ -2061,7 +2135,8 @@ impl Database { connection: ConnectionId, worktrees: &[proto::WorktreeMetadata], ) -> Result)>> { - self.room_transaction(|tx| async move { + let room_id = self.room_id_for_project(project_id).await?; + self.room_transaction(room_id, |tx| async move { let project = project::Entity::find_by_id(project_id) .filter( Condition::all() @@ -2079,7 +2154,7 @@ impl Database { let guest_connection_ids = self.project_guest_connection_ids(project.id, &tx).await?; let room = self.get_room(project.room_id, &tx).await?; - Ok((project.room_id, (room, guest_connection_ids))) + Ok((room, guest_connection_ids)) }) .await } @@ -2124,12 +2199,12 @@ impl Database { update: &proto::UpdateWorktree, connection: ConnectionId, ) -> Result>> { - self.room_transaction(|tx| async move { - let project_id = ProjectId::from_proto(update.project_id); - let worktree_id = update.worktree_id as i64; - + let project_id = ProjectId::from_proto(update.project_id); + let worktree_id = update.worktree_id as i64; + let room_id = self.room_id_for_project(project_id).await?; + self.room_transaction(room_id, |tx| async move { // Ensure the update comes from the host. - let project = project::Entity::find_by_id(project_id) + let _project = project::Entity::find_by_id(project_id) .filter( Condition::all() .add(project::Column::HostConnectionId.eq(connection.id as i32)) @@ -2140,7 +2215,6 @@ impl Database { .one(&*tx) .await? .ok_or_else(|| anyhow!("no such project"))?; - let room_id = project.room_id; // Update metadata. worktree::Entity::update(worktree::ActiveModel { @@ -2220,7 +2294,7 @@ impl Database { } let connection_ids = self.project_guest_connection_ids(project_id, &tx).await?; - Ok((room_id, connection_ids)) + Ok(connection_ids) }) .await } @@ -2230,9 +2304,10 @@ impl Database { update: &proto::UpdateDiagnosticSummary, connection: ConnectionId, ) -> Result>> { - self.room_transaction(|tx| async move { - let project_id = ProjectId::from_proto(update.project_id); - let worktree_id = update.worktree_id as i64; + let project_id = ProjectId::from_proto(update.project_id); + let worktree_id = update.worktree_id as i64; + let room_id = self.room_id_for_project(project_id).await?; + self.room_transaction(room_id, |tx| async move { let summary = update .summary .as_ref() @@ -2274,7 +2349,7 @@ impl Database { .await?; let connection_ids = self.project_guest_connection_ids(project_id, &tx).await?; - Ok((project.room_id, connection_ids)) + Ok(connection_ids) }) .await } @@ -2284,8 +2359,9 @@ impl Database { update: &proto::StartLanguageServer, connection: ConnectionId, ) -> Result>> { - self.room_transaction(|tx| async move { - let project_id = ProjectId::from_proto(update.project_id); + let project_id = ProjectId::from_proto(update.project_id); + let room_id = self.room_id_for_project(project_id).await?; + self.room_transaction(room_id, |tx| async move { let server = update .server .as_ref() @@ -2319,7 +2395,7 @@ impl Database { .await?; let connection_ids = self.project_guest_connection_ids(project_id, &tx).await?; - Ok((project.room_id, connection_ids)) + Ok(connection_ids) }) .await } @@ -2329,7 +2405,8 @@ impl Database { project_id: ProjectId, connection: ConnectionId, ) -> Result> { - self.room_transaction(|tx| async move { + let room_id = self.room_id_for_project(project_id).await?; + self.room_transaction(room_id, |tx| async move { let participant = room_participant::Entity::find() .filter( Condition::all() @@ -2455,7 +2532,6 @@ impl Database { .all(&*tx) .await?; - let room_id = project.room_id; let project = Project { collaborators: collaborators .into_iter() @@ -2475,7 +2551,7 @@ impl Database { }) .collect(), }; - Ok((room_id, (project, replica_id as ReplicaId))) + Ok((project, replica_id as ReplicaId)) }) .await } @@ -2485,7 +2561,8 @@ impl Database { project_id: ProjectId, connection: ConnectionId, ) -> Result> { - self.room_transaction(|tx| async move { + let room_id = self.room_id_for_project(project_id).await?; + self.room_transaction(room_id, |tx| async move { let result = project_collaborator::Entity::delete_many() .filter( Condition::all() @@ -2521,7 +2598,7 @@ impl Database { host_connection_id: project.host_connection()?, connection_ids, }; - Ok((project.room_id, left_project)) + Ok(left_project) }) .await } @@ -2531,11 +2608,8 @@ impl Database { project_id: ProjectId, connection_id: ConnectionId, ) -> Result>> { - self.room_transaction(|tx| async move { - let project = project::Entity::find_by_id(project_id) - .one(&*tx) - .await? - .ok_or_else(|| anyhow!("no such project"))?; + let room_id = self.room_id_for_project(project_id).await?; + self.room_transaction(room_id, |tx| async move { let collaborators = project_collaborator::Entity::find() .filter(project_collaborator::Column::ProjectId.eq(project_id)) .all(&*tx) @@ -2553,7 +2627,7 @@ impl Database { .iter() .any(|collaborator| collaborator.connection_id == connection_id) { - Ok((project.room_id, collaborators)) + Ok(collaborators) } else { Err(anyhow!("no such project"))? } @@ -2566,11 +2640,8 @@ impl Database { project_id: ProjectId, connection_id: ConnectionId, ) -> Result>> { - self.room_transaction(|tx| async move { - let project = project::Entity::find_by_id(project_id) - .one(&*tx) - .await? - .ok_or_else(|| anyhow!("no such project"))?; + let room_id = self.room_id_for_project(project_id).await?; + self.room_transaction(room_id, |tx| async move { let mut collaborators = project_collaborator::Entity::find() .filter(project_collaborator::Column::ProjectId.eq(project_id)) .stream(&*tx) @@ -2583,7 +2654,7 @@ impl Database { } if connection_ids.contains(&connection_id) { - Ok((project.room_id, connection_ids)) + Ok(connection_ids) } else { Err(anyhow!("no such project"))? } @@ -2613,6 +2684,17 @@ impl Database { Ok(guest_connection_ids) } + async fn room_id_for_project(&self, project_id: ProjectId) -> Result { + self.transaction(|tx| async move { + let project = project::Entity::find_by_id(project_id) + .one(&*tx) + .await? + .ok_or_else(|| anyhow!("project {} not found", project_id))?; + Ok(project.room_id) + }) + .await + } + // access tokens pub async fn create_access_token_hash( @@ -2763,21 +2845,48 @@ impl Database { self.run(body).await } - async fn room_transaction(&self, f: F) -> Result> + async fn room_transaction(&self, room_id: RoomId, f: F) -> Result> where F: Send + Fn(TransactionHandle) -> Fut, - Fut: Send + Future>, + Fut: Send + Future>, { - let data = self - .optional_room_transaction(move |tx| { - let future = f(tx); - async { - let data = future.await?; - Ok(Some(data)) + let body = async { + loop { + let lock = self.rooms.entry(room_id).or_default().clone(); + let _guard = lock.lock_owned().await; + let (tx, result) = self.with_transaction(&f).await?; + match result { + Ok(data) => { + match tx.commit().await.map_err(Into::into) { + Ok(()) => { + return Ok(RoomGuard { + data, + _guard, + _not_send: PhantomData, + }); + } + Err(error) => { + if is_serialization_error(&error) { + // Retry (don't break the loop) + } else { + return Err(error); + } + } + } + } + Err(error) => { + tx.rollback().await?; + if is_serialization_error(&error) { + // Retry (don't break the loop) + } else { + return Err(error); + } + } } - }) - .await?; - Ok(data.unwrap()) + } + }; + + self.run(body).await } async fn with_transaction(&self, f: &F) -> Result<(DatabaseTransaction, Result)> @@ -3011,6 +3120,7 @@ macro_rules! id_type { id_type!(AccessTokenId); id_type!(ContactId); +id_type!(FollowerId); id_type!(RoomId); id_type!(RoomParticipantId); id_type!(ProjectId); diff --git a/crates/collab/src/db/follower.rs b/crates/collab/src/db/follower.rs new file mode 100644 index 0000000000000000000000000000000000000000..f1243dc99eb34371bdd433c431b8a12eafeadbb6 --- /dev/null +++ b/crates/collab/src/db/follower.rs @@ -0,0 +1,51 @@ +use super::{FollowerId, ProjectId, RoomId, ServerId}; +use rpc::ConnectionId; +use sea_orm::entity::prelude::*; +use serde::Serialize; + +#[derive(Clone, Debug, Default, PartialEq, Eq, DeriveEntityModel, Serialize)] +#[sea_orm(table_name = "followers")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: FollowerId, + pub room_id: RoomId, + pub project_id: ProjectId, + pub leader_connection_server_id: ServerId, + pub leader_connection_id: i32, + pub follower_connection_server_id: ServerId, + pub follower_connection_id: i32, +} + +impl Model { + pub fn leader_connection(&self) -> ConnectionId { + ConnectionId { + owner_id: self.leader_connection_server_id.0 as u32, + id: self.leader_connection_id as u32, + } + } + + pub fn follower_connection(&self) -> ConnectionId { + ConnectionId { + owner_id: self.follower_connection_server_id.0 as u32, + id: self.follower_connection_id as u32, + } + } +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::room::Entity", + from = "Column::RoomId", + to = "super::room::Column::Id" + )] + Room, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Room.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/crates/collab/src/db/room.rs b/crates/collab/src/db/room.rs index 7dbf03a780adbd69c1d3b492e4bcf82557ae70ab..c3e88670ebe00afa523b008e6bf669de01ec1d1e 100644 --- a/crates/collab/src/db/room.rs +++ b/crates/collab/src/db/room.rs @@ -15,6 +15,8 @@ pub enum Relation { RoomParticipant, #[sea_orm(has_many = "super::project::Entity")] Project, + #[sea_orm(has_many = "super::follower::Entity")] + Follower, } impl Related for Entity { @@ -29,4 +31,10 @@ impl Related for Entity { } } +impl Related for Entity { + fn to() -> RelationDef { + Relation::Follower.def() + } +} + impl ActiveModelBehavior for ActiveModel {} diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 32cce1e6815451ce9ac7b8e6e261ed60bca41e3d..01d24024eb7516e87e4c0477d069cae8feb42da6 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -270,8 +270,11 @@ impl Server { let mut live_kit_room = String::new(); let mut delete_live_kit_room = false; - if let Ok(mut refreshed_room) = - app_state.db.refresh_room(room_id, server_id).await + if let Some(mut refreshed_room) = app_state + .db + .refresh_room(room_id, server_id) + .await + .trace_err() { tracing::info!( room_id = room_id.0, @@ -1312,6 +1315,7 @@ async fn join_project( .filter(|collaborator| collaborator.connection_id != session.connection_id) .map(|collaborator| collaborator.to_proto()) .collect::>(); + let worktrees = project .worktrees .iter() @@ -1724,6 +1728,7 @@ async fn follow( .ok_or_else(|| anyhow!("invalid leader id"))? .into(); let follower_id = session.connection_id; + { let project_connection_ids = session .db() @@ -1744,6 +1749,14 @@ async fn follow( .views .retain(|view| view.leader_id != Some(follower_id.into())); response.send(response_payload)?; + + let room = session + .db() + .await + .follow(project_id, leader_id, follower_id) + .await?; + room_updated(&room, &session.peer); + Ok(()) } @@ -1753,17 +1766,29 @@ async fn unfollow(request: proto::Unfollow, session: Session) -> Result<()> { .leader_id .ok_or_else(|| anyhow!("invalid leader id"))? .into(); - let project_connection_ids = session + let follower_id = session.connection_id; + + if !session .db() .await .project_connection_ids(project_id, session.connection_id) - .await?; - if !project_connection_ids.contains(&leader_id) { + .await? + .contains(&leader_id) + { Err(anyhow!("no such peer"))?; } + session .peer .forward_send(session.connection_id, leader_id, request)?; + + let room = session + .db() + .await + .unfollow(project_id, leader_id, follower_id) + .await?; + room_updated(&room, &session.peer); + Ok(()) } diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 2fc19b005b7ded5a1212fb09e41f545d7191503c..4c8f9781641e7030d3fb326fcc64eaccd7dc70a3 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -733,6 +733,14 @@ async fn test_server_restarts( deterministic.forbid_parking(); let mut server = TestServer::start(&deterministic).await; let client_a = server.create_client(cx_a, "user_a").await; + client_a + .fs + .insert_tree("/a", json!({ "a.txt": "a-contents" })) + .await; + + // Invite client B to collaborate on a project + let (project_a, _) = client_a.build_local_project("/a", cx_a).await; + let client_b = server.create_client(cx_b, "user_b").await; let client_c = server.create_client(cx_c, "user_c").await; let client_d = server.create_client(cx_d, "user_d").await; @@ -753,19 +761,19 @@ async fn test_server_restarts( // User A calls users B, C, and D. active_call_a .update(cx_a, |call, cx| { - call.invite(client_b.user_id().unwrap(), None, cx) + call.invite(client_b.user_id().unwrap(), Some(project_a.clone()), cx) }) .await .unwrap(); active_call_a .update(cx_a, |call, cx| { - call.invite(client_c.user_id().unwrap(), None, cx) + call.invite(client_c.user_id().unwrap(), Some(project_a.clone()), cx) }) .await .unwrap(); active_call_a .update(cx_a, |call, cx| { - call.invite(client_d.user_id().unwrap(), None, cx) + call.invite(client_d.user_id().unwrap(), Some(project_a.clone()), cx) }) .await .unwrap(); @@ -1083,7 +1091,7 @@ async fn test_calls_on_multiple_connections( assert!(incoming_call_b2.next().await.unwrap().is_none()); // User B disconnects the client that is not on the call. Everything should be fine. - client_b1.disconnect(&cx_b1.to_async()).unwrap(); + client_b1.disconnect(&cx_b1.to_async()); deterministic.advance_clock(RECEIVE_TIMEOUT); client_b1 .authenticate_and_connect(false, &cx_b1.to_async()) @@ -3227,7 +3235,7 @@ async fn test_leaving_project( buffer_b2.read_with(cx_b, |buffer, _| assert_eq!(buffer.text(), "a-contents")); // Drop client B's connection and ensure client A and client C observe client B leaving. - client_b.disconnect(&cx_b.to_async()).unwrap(); + client_b.disconnect(&cx_b.to_async()); deterministic.advance_clock(RECONNECT_TIMEOUT); project_a.read_with(cx_a, |project, _| { assert_eq!(project.collaborators().len(), 1); @@ -5772,7 +5780,7 @@ async fn test_contact_requests( .is_empty()); async fn disconnect_and_reconnect(client: &TestClient, cx: &mut TestAppContext) { - client.disconnect(&cx.to_async()).unwrap(); + client.disconnect(&cx.to_async()); client.clear_contacts(cx).await; client .authenticate_and_connect(false, &cx.to_async()) @@ -5786,6 +5794,7 @@ async fn test_following( deterministic: Arc, cx_a: &mut TestAppContext, cx_b: &mut TestAppContext, + cx_c: &mut TestAppContext, ) { deterministic.forbid_parking(); cx_a.update(editor::init); @@ -5794,9 +5803,13 @@ async fn test_following( let mut server = TestServer::start(&deterministic).await; let client_a = server.create_client(cx_a, "user_a").await; let client_b = server.create_client(cx_b, "user_b").await; + let client_c = server.create_client(cx_c, "user_c").await; server .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)]) .await; + server + .make_contacts(&mut [(&client_a, cx_a), (&client_c, cx_c)]) + .await; let active_call_a = cx_a.read(ActiveCall::global); let active_call_b = cx_b.read(ActiveCall::global); @@ -5827,8 +5840,10 @@ async fn test_following( .await .unwrap(); - // Client A opens some editors. let workspace_a = client_a.build_workspace(&project_a, cx_a); + let workspace_b = client_b.build_workspace(&project_b, cx_b); + + // Client A opens some editors. let pane_a = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone()); let editor_a1 = workspace_a .update(cx_a, |workspace, cx| { @@ -5848,7 +5863,6 @@ async fn test_following( .unwrap(); // Client B opens an editor. - let workspace_b = client_b.build_workspace(&project_b, cx_b); let editor_b1 = workspace_b .update(cx_b, |workspace, cx| { workspace.open_path((worktree_id, "1.txt"), None, true, cx) @@ -5858,29 +5872,97 @@ async fn test_following( .downcast::() .unwrap(); - let client_a_id = project_b.read_with(cx_b, |project, _| { - project.collaborators().values().next().unwrap().peer_id - }); - let client_b_id = project_a.read_with(cx_a, |project, _| { - project.collaborators().values().next().unwrap().peer_id - }); + let peer_id_a = client_a.peer_id().unwrap(); + let peer_id_b = client_b.peer_id().unwrap(); + let peer_id_c = client_c.peer_id().unwrap(); - // When client B starts following client A, all visible view states are replicated to client B. + // Client A updates their selections in those editors editor_a1.update(cx_a, |editor, cx| { editor.change_selections(None, cx, |s| s.select_ranges([0..1])) }); editor_a2.update(cx_a, |editor, cx| { editor.change_selections(None, cx, |s| s.select_ranges([2..3])) }); + + // When client B starts following client A, all visible view states are replicated to client B. workspace_b .update(cx_b, |workspace, cx| { workspace - .toggle_follow(&ToggleFollow(client_a_id), cx) + .toggle_follow(&ToggleFollow(peer_id_a), cx) .unwrap() }) .await .unwrap(); + // Client A invites client C to the call. + active_call_a + .update(cx_a, |call, cx| { + call.invite(client_c.current_user_id(cx_c).to_proto(), None, cx) + }) + .await + .unwrap(); + cx_c.foreground().run_until_parked(); + let active_call_c = cx_c.read(ActiveCall::global); + active_call_c + .update(cx_c, |call, cx| call.accept_incoming(cx)) + .await + .unwrap(); + let project_c = client_c.build_remote_project(project_id, cx_c).await; + let workspace_c = client_c.build_workspace(&project_c, cx_c); + active_call_c + .update(cx_c, |call, cx| call.set_location(Some(&project_c), cx)) + .await + .unwrap(); + + // Client C also follows client A. + workspace_c + .update(cx_c, |workspace, cx| { + workspace + .toggle_follow(&ToggleFollow(peer_id_a), cx) + .unwrap() + }) + .await + .unwrap(); + + // All clients see that clients B and C are following client A. + cx_c.foreground().run_until_parked(); + for (name, active_call, cx) in [ + ("A", &active_call_a, &cx_a), + ("B", &active_call_b, &cx_b), + ("C", &active_call_c, &cx_c), + ] { + active_call.read_with(*cx, |call, cx| { + let room = call.room().unwrap().read(cx); + assert_eq!( + room.followers_for(peer_id_a, project_id), + &[peer_id_b, peer_id_c], + "checking followers for A as {name}" + ); + }); + } + + // Client C unfollows client A. + workspace_c.update(cx_c, |workspace, cx| { + workspace.toggle_follow(&ToggleFollow(peer_id_a), cx); + }); + + // All clients see that clients B is following client A. + cx_c.foreground().run_until_parked(); + for (name, active_call, cx) in [ + ("A", &active_call_a, &cx_a), + ("B", &active_call_b, &cx_b), + ("C", &active_call_c, &cx_c), + ] { + active_call.read_with(*cx, |call, cx| { + let room = call.room().unwrap().read(cx); + assert_eq!( + room.followers_for(peer_id_a, project_id), + &[peer_id_b], + "checking followers for A as {name}" + ); + }); + } + let editor_b2 = workspace_b.read_with(cx_b, |workspace, cx| { workspace .active_item(cx) @@ -6033,14 +6115,14 @@ async fn test_following( workspace_a .update(cx_a, |workspace, cx| { workspace - .toggle_follow(&ToggleFollow(client_b_id), cx) + .toggle_follow(&ToggleFollow(peer_id_b), cx) .unwrap() }) .await .unwrap(); assert_eq!( workspace_a.read_with(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)), - Some(client_b_id) + Some(peer_id_b) ); assert_eq!( workspace_a.read_with(cx_a, |workspace, cx| workspace @@ -6112,7 +6194,7 @@ async fn test_following( ); // Following interrupts when client B disconnects. - client_b.disconnect(&cx_b.to_async()).unwrap(); + client_b.disconnect(&cx_b.to_async()); deterministic.advance_clock(RECONNECT_TIMEOUT); assert_eq!( workspace_a.read_with(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)), diff --git a/crates/collab_ui/Cargo.toml b/crates/collab_ui/Cargo.toml index 2dc4cc769a80c5fae0aa6b1e508bf259073f427a..899f8cc8b4204dc0c2b6d0c060aafb1a42e7e907 100644 --- a/crates/collab_ui/Cargo.toml +++ b/crates/collab_ui/Cargo.toml @@ -27,6 +27,7 @@ call = { path = "../call" } client = { path = "../client" } clock = { path = "../clock" } collections = { path = "../collections" } +context_menu = { path = "../context_menu" } editor = { path = "../editor" } fuzzy = { path = "../fuzzy" } gpui = { path = "../gpui" } diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index a767e5056564ac42cc004ec2b6047d3919f17eb1..f9f5738ad26762dd9e4fc5c08c6cf48439b90e67 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -1,33 +1,60 @@ -use crate::{contact_notification::ContactNotification, contacts_popover, ToggleScreenSharing}; -use call::{ActiveCall, ParticipantLocation}; -use client::{proto::PeerId, Authenticate, ContactEventKind, User, UserStore}; +use crate::{ + collaborator_list_popover, collaborator_list_popover::CollaboratorListPopover, + contact_notification::ContactNotification, contacts_popover, face_pile::FacePile, + ToggleScreenSharing, +}; +use call::{ActiveCall, ParticipantLocation, Room}; +use client::{proto::PeerId, Authenticate, ContactEventKind, SignOut, User, UserStore}; use clock::ReplicaId; use contacts_popover::ContactsPopover; +use context_menu::{ContextMenu, ContextMenuItem}; use gpui::{ actions, color::Color, elements::*, geometry::{rect::RectF, vector::vec2f, PathBuilder}, + impl_internal_actions, json::{self, ToJson}, - Border, CursorStyle, Entity, ModelHandle, MouseButton, MutableAppContext, RenderContext, + CursorStyle, Entity, ImageData, ModelHandle, MouseButton, MutableAppContext, RenderContext, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, }; use settings::Settings; -use std::ops::Range; -use theme::Theme; +use std::{ops::Range, sync::Arc}; +use theme::{AvatarStyle, Theme}; +use util::ResultExt; use workspace::{FollowNextCollaborator, JoinProject, ToggleFollow, Workspace}; -actions!(collab, [ToggleCollaborationMenu, ShareProject]); +actions!( + collab, + [ + ToggleCollaboratorList, + ToggleContactsMenu, + ToggleUserMenu, + ShareProject, + UnshareProject, + ] +); + +impl_internal_actions!(collab, [LeaveCall]); + +#[derive(Copy, Clone, PartialEq)] +pub(crate) struct LeaveCall; pub fn init(cx: &mut MutableAppContext) { + cx.add_action(CollabTitlebarItem::toggle_collaborator_list_popover); cx.add_action(CollabTitlebarItem::toggle_contacts_popover); cx.add_action(CollabTitlebarItem::share_project); + cx.add_action(CollabTitlebarItem::unshare_project); + cx.add_action(CollabTitlebarItem::leave_call); + cx.add_action(CollabTitlebarItem::toggle_user_menu); } pub struct CollabTitlebarItem { workspace: WeakViewHandle, user_store: ModelHandle, contacts_popover: Option>, + user_menu: ViewHandle, + collaborator_list_popover: Option>, _subscriptions: Vec, } @@ -47,27 +74,61 @@ impl View for CollabTitlebarItem { return Empty::new().boxed(); }; + let project = workspace.read(cx).project().read(cx); + let mut project_title = String::new(); + for (i, name) in project.worktree_root_names(cx).enumerate() { + if i > 0 { + project_title.push_str(", "); + } + project_title.push_str(name); + } + if project_title.is_empty() { + project_title = "empty project".to_owned(); + } + let theme = cx.global::().theme.clone(); - let mut container = Flex::row(); + let mut left_container = Flex::row(); + let mut right_container = Flex::row(); - container.add_children(self.render_toggle_screen_sharing_button(&theme, cx)); + left_container.add_child( + Label::new(project_title, theme.workspace.titlebar.title.clone()) + .contained() + .with_margin_right(theme.workspace.titlebar.item_spacing) + .aligned() + .left() + .boxed(), + ); - if workspace.read(cx).client().status().borrow().is_connected() { - let project = workspace.read(cx).project().read(cx); - if project.is_shared() - || project.is_remote() - || ActiveCall::global(cx).read(cx).room().is_none() - { - container.add_child(self.render_toggle_contacts_button(&theme, cx)); - } else { - container.add_child(self.render_share_button(&theme, cx)); - } + let user = workspace.read(cx).user_store().read(cx).current_user(); + let peer_id = workspace.read(cx).client().peer_id(); + if let Some(((user, peer_id), room)) = user + .zip(peer_id) + .zip(ActiveCall::global(cx).read(cx).room().cloned()) + { + left_container + .add_children(self.render_in_call_share_unshare_button(&workspace, &theme, cx)); + + right_container.add_children(self.render_collaborators(&workspace, &theme, &room, cx)); + right_container + .add_child(self.render_current_user(&workspace, &theme, &user, peer_id, cx)); + right_container.add_child(self.render_toggle_screen_sharing_button(&theme, &room, cx)); + } + + let status = workspace.read(cx).client().status(); + let status = &*status.borrow(); + if matches!(status, client::Status::Connected { .. }) { + right_container.add_child(self.render_toggle_contacts_button(&theme, cx)); + } else { + right_container.add_children(self.render_connection_status(status, cx)); } - container.add_children(self.render_collaborators(&workspace, &theme, cx)); - container.add_children(self.render_current_user(&workspace, &theme, cx)); - container.add_children(self.render_connection_status(&workspace, cx)); - container.boxed() + + right_container.add_child(self.render_user_menu_button(&theme, cx)); + + Stack::new() + .with_child(left_container.boxed()) + .with_child(right_container.aligned().right().boxed()) + .boxed() } } @@ -80,7 +141,7 @@ impl CollabTitlebarItem { let active_call = ActiveCall::global(cx); let mut subscriptions = Vec::new(); subscriptions.push(cx.observe(workspace, |_, _, cx| cx.notify())); - subscriptions.push(cx.observe(&active_call, |_, _, cx| cx.notify())); + subscriptions.push(cx.observe(&active_call, |this, _, cx| this.active_call_changed(cx))); subscriptions.push(cx.observe_window_activation(|this, active, cx| { this.window_activation_changed(active, cx) })); @@ -112,6 +173,12 @@ impl CollabTitlebarItem { workspace: workspace.downgrade(), user_store: user_store.clone(), contacts_popover: None, + user_menu: cx.add_view(|cx| { + let mut menu = ContextMenu::new(cx); + menu.set_position_mode(OverlayPositionMode::Local); + menu + }), + collaborator_list_popover: None, _subscriptions: subscriptions, } } @@ -129,6 +196,13 @@ impl CollabTitlebarItem { } } + fn active_call_changed(&mut self, cx: &mut ViewContext) { + if ActiveCall::global(cx).read(cx).room().is_none() { + self.contacts_popover = None; + } + cx.notify(); + } + fn share_project(&mut self, _: &ShareProject, cx: &mut ViewContext) { if let Some(workspace) = self.workspace.upgrade(cx) { let active_call = ActiveCall::global(cx); @@ -139,41 +213,135 @@ impl CollabTitlebarItem { } } - pub fn toggle_contacts_popover( + fn unshare_project(&mut self, _: &UnshareProject, cx: &mut ViewContext) { + if let Some(workspace) = self.workspace.upgrade(cx) { + let active_call = ActiveCall::global(cx); + let project = workspace.read(cx).project().clone(); + active_call + .update(cx, |call, cx| call.unshare_project(project, cx)) + .log_err(); + } + } + + pub fn toggle_collaborator_list_popover( &mut self, - _: &ToggleCollaborationMenu, + _: &ToggleCollaboratorList, cx: &mut ViewContext, ) { - match self.contacts_popover.take() { + match self.collaborator_list_popover.take() { Some(_) => {} None => { if let Some(workspace) = self.workspace.upgrade(cx) { - let project = workspace.read(cx).project().clone(); let user_store = workspace.read(cx).user_store().clone(); - let view = cx.add_view(|cx| ContactsPopover::new(project, user_store, cx)); + let view = cx.add_view(|cx| CollaboratorListPopover::new(user_store, cx)); + cx.subscribe(&view, |this, _, event, cx| { match event { - contacts_popover::Event::Dismissed => { - this.contacts_popover = None; + collaborator_list_popover::Event::Dismissed => { + this.collaborator_list_popover = None; } } cx.notify(); }) .detach(); - self.contacts_popover = Some(view); + + self.collaborator_list_popover = Some(view); } } } cx.notify(); } + pub fn toggle_contacts_popover(&mut self, _: &ToggleContactsMenu, cx: &mut ViewContext) { + if self.contacts_popover.take().is_none() { + if let Some(workspace) = self.workspace.upgrade(cx) { + let project = workspace.read(cx).project().clone(); + let user_store = workspace.read(cx).user_store().clone(); + let view = cx.add_view(|cx| ContactsPopover::new(project, user_store, cx)); + cx.subscribe(&view, |this, _, event, cx| { + match event { + contacts_popover::Event::Dismissed => { + this.contacts_popover = None; + } + } + + cx.notify(); + }) + .detach(); + self.contacts_popover = Some(view); + } + } + + cx.notify(); + } + + pub fn toggle_user_menu(&mut self, _: &ToggleUserMenu, cx: &mut ViewContext) { + let theme = cx.global::().theme.clone(); + let avatar_style = theme.workspace.titlebar.leader_avatar.clone(); + let item_style = theme.context_menu.item.disabled_style().clone(); + self.user_menu.update(cx, |user_menu, cx| { + let items = if let Some(user) = self.user_store.read(cx).current_user() { + vec![ + ContextMenuItem::Static(Box::new(move |_| { + Flex::row() + .with_children(user.avatar.clone().map(|avatar| { + Self::render_face( + avatar, + avatar_style.clone(), + Color::transparent_black(), + ) + })) + .with_child( + Label::new(user.github_login.clone(), item_style.label.clone()) + .boxed(), + ) + .contained() + .with_style(item_style.container) + .boxed() + })), + ContextMenuItem::Item { + label: "Sign out".into(), + action: Box::new(SignOut), + }, + ] + } else { + vec![ContextMenuItem::Item { + label: "Sign in".into(), + action: Box::new(Authenticate), + }] + }; + + user_menu.show( + vec2f( + theme + .workspace + .titlebar + .user_menu_button + .default + .button_width, + theme.workspace.titlebar.height, + ), + AnchorCorner::TopRight, + items, + cx, + ); + }); + } + + fn leave_call(&mut self, _: &LeaveCall, cx: &mut ViewContext) { + ActiveCall::global(cx) + .update(cx, |call, cx| call.hang_up(cx)) + .log_err(); + } + fn render_toggle_contacts_button( &self, theme: &Theme, cx: &mut RenderContext, ) -> ElementBox { let titlebar = &theme.workspace.titlebar; + let badge = if self .user_store .read(cx) @@ -194,9 +362,10 @@ impl CollabTitlebarItem { .boxed(), ) }; + Stack::new() .with_child( - MouseEventHandler::::new(0, cx, |state, _| { + MouseEventHandler::::new(0, cx, |state, _| { let style = titlebar .toggle_contacts_button .style_for(state, self.contacts_popover.is_some()); @@ -214,39 +383,31 @@ impl CollabTitlebarItem { }) .with_cursor_style(CursorStyle::PointingHand) .on_click(MouseButton::Left, move |_, cx| { - cx.dispatch_action(ToggleCollaborationMenu); + cx.dispatch_action(ToggleContactsMenu); }) + .with_tooltip::( + 0, + "Show contacts menu".into(), + Some(Box::new(ToggleContactsMenu)), + theme.tooltip.clone(), + cx, + ) .aligned() .boxed(), ) .with_children(badge) - .with_children(self.contacts_popover.as_ref().map(|popover| { - Overlay::new( - ChildView::new(popover, cx) - .contained() - .with_margin_top(titlebar.height) - .with_margin_left(titlebar.toggle_contacts_button.default.button_width) - .with_margin_right(-titlebar.toggle_contacts_button.default.button_width) - .boxed(), - ) - .with_fit_mode(OverlayFitMode::SwitchAnchor) - .with_anchor_corner(AnchorCorner::BottomLeft) - .with_z_index(999) - .boxed() - })) + .with_children(self.render_contacts_popover_host(titlebar, cx)) .boxed() } fn render_toggle_screen_sharing_button( &self, theme: &Theme, + room: &ModelHandle, cx: &mut RenderContext, - ) -> Option { - let active_call = ActiveCall::global(cx); - let room = active_call.read(cx).room().cloned()?; + ) -> ElementBox { let icon; let tooltip; - if room.read(cx).is_screen_sharing() { icon = "icons/disable_screen_sharing_12.svg"; tooltip = "Stop Sharing Screen" @@ -256,226 +417,368 @@ impl CollabTitlebarItem { } let titlebar = &theme.workspace.titlebar; - Some( - MouseEventHandler::::new(0, cx, |state, _| { - let style = titlebar.call_control.style_for(state, false); - Svg::new(icon) - .with_color(style.color) - .constrained() - .with_width(style.icon_width) - .aligned() - .constrained() - .with_width(style.button_width) - .with_height(style.button_width) - .contained() - .with_style(style.container) - .boxed() - }) - .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, move |_, cx| { - cx.dispatch_action(ToggleScreenSharing); - }) - .with_tooltip::( - 0, - tooltip.into(), - Some(Box::new(ToggleScreenSharing)), - theme.tooltip.clone(), - cx, - ) - .aligned() - .boxed(), - ) - } - - fn render_share_button(&self, theme: &Theme, cx: &mut RenderContext) -> ElementBox { - enum Share {} - - let titlebar = &theme.workspace.titlebar; - MouseEventHandler::::new(0, cx, |state, _| { - let style = titlebar.share_button.style_for(state, false); - Label::new("Share", style.text.clone()) + MouseEventHandler::::new(0, cx, |state, _| { + let style = titlebar.call_control.style_for(state, false); + Svg::new(icon) + .with_color(style.color) + .constrained() + .with_width(style.icon_width) + .aligned() + .constrained() + .with_width(style.button_width) + .with_height(style.button_width) .contained() .with_style(style.container) .boxed() }) .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, |_, cx| cx.dispatch_action(ShareProject)) - .with_tooltip::( + .on_click(MouseButton::Left, move |_, cx| { + cx.dispatch_action(ToggleScreenSharing); + }) + .with_tooltip::( 0, - "Share project with call participants".into(), - None, + tooltip.into(), + Some(Box::new(ToggleScreenSharing)), theme.tooltip.clone(), cx, ) .aligned() - .contained() - .with_margin_left(theme.workspace.titlebar.avatar_margin) .boxed() } + fn render_in_call_share_unshare_button( + &self, + workspace: &ViewHandle, + theme: &Theme, + cx: &mut RenderContext, + ) -> Option { + let project = workspace.read(cx).project(); + if project.read(cx).is_remote() { + return None; + } + + let is_shared = project.read(cx).is_shared(); + let label = if is_shared { "Unshare" } else { "Share" }; + let tooltip = if is_shared { + "Unshare project from call participants" + } else { + "Share project with call participants" + }; + + let titlebar = &theme.workspace.titlebar; + + enum ShareUnshare {} + Some( + Stack::new() + .with_child( + MouseEventHandler::::new(0, cx, |state, _| { + //TODO: Ensure this button has consistant width for both text variations + let style = titlebar + .share_button + .style_for(state, self.contacts_popover.is_some()); + Label::new(label, style.text.clone()) + .contained() + .with_style(style.container) + .boxed() + }) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, move |_, cx| { + if is_shared { + cx.dispatch_action(UnshareProject); + } else { + cx.dispatch_action(ShareProject); + } + }) + .with_tooltip::( + 0, + tooltip.to_owned(), + None, + theme.tooltip.clone(), + cx, + ) + .boxed(), + ) + .aligned() + .contained() + .with_margin_left(theme.workspace.titlebar.item_spacing) + .boxed(), + ) + } + + fn render_user_menu_button(&self, theme: &Theme, cx: &mut RenderContext) -> ElementBox { + let titlebar = &theme.workspace.titlebar; + + Stack::new() + .with_child( + MouseEventHandler::::new(0, cx, |state, _| { + let style = titlebar.call_control.style_for(state, false); + Svg::new("icons/ellipsis_14.svg") + .with_color(style.color) + .constrained() + .with_width(style.icon_width) + .aligned() + .constrained() + .with_width(style.button_width) + .with_height(style.button_width) + .contained() + .with_style(style.container) + .boxed() + }) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, move |_, cx| { + cx.dispatch_action(ToggleUserMenu); + }) + .with_tooltip::( + 0, + "Toggle user menu".to_owned(), + Some(Box::new(ToggleUserMenu)), + theme.tooltip.clone(), + cx, + ) + .contained() + .with_margin_left(theme.workspace.titlebar.item_spacing) + .aligned() + .boxed(), + ) + .with_child(ChildView::new(&self.user_menu, cx).boxed()) + .boxed() + } + + fn render_contacts_popover_host<'a>( + &'a self, + theme: &'a theme::Titlebar, + cx: &'a RenderContext, + ) -> Option { + self.contacts_popover.as_ref().map(|popover| { + Overlay::new( + ChildView::new(popover, cx) + .contained() + .with_margin_top(theme.height) + .with_margin_left(theme.toggle_contacts_button.default.button_width) + .with_margin_right(-theme.toggle_contacts_button.default.button_width) + .boxed(), + ) + .with_fit_mode(OverlayFitMode::SwitchAnchor) + .with_anchor_corner(AnchorCorner::BottomLeft) + .with_z_index(999) + .boxed() + }) + } + fn render_collaborators( &self, workspace: &ViewHandle, theme: &Theme, + room: &ModelHandle, cx: &mut RenderContext, ) -> Vec { - let active_call = ActiveCall::global(cx); - if let Some(room) = active_call.read(cx).room().cloned() { - let project = workspace.read(cx).project().read(cx); - let mut participants = room - .read(cx) - .remote_participants() - .values() - .cloned() - .collect::>(); - participants.sort_by_key(|p| Some(project.collaborators().get(&p.peer_id)?.replica_id)); - participants - .into_iter() - .filter_map(|participant| { - let project = workspace.read(cx).project().read(cx); - let replica_id = project - .collaborators() - .get(&participant.peer_id) - .map(|collaborator| collaborator.replica_id); - let user = participant.user.clone(); - Some(self.render_avatar( + let project = workspace.read(cx).project().read(cx); + + let mut participants = room + .read(cx) + .remote_participants() + .values() + .cloned() + .collect::>(); + participants.sort_by_key(|p| Some(project.collaborators().get(&p.peer_id)?.replica_id)); + + participants + .into_iter() + .filter_map(|participant| { + let project = workspace.read(cx).project().read(cx); + let replica_id = project + .collaborators() + .get(&participant.peer_id) + .map(|collaborator| collaborator.replica_id); + let user = participant.user.clone(); + Some( + Container::new(self.render_face_pile( &user, replica_id, - Some(( - participant.peer_id, - &user.github_login, - participant.location, - )), + participant.peer_id, + Some(participant.location), workspace, theme, cx, )) - }) - .collect() - } else { - Default::default() - } + .with_margin_right(theme.workspace.titlebar.face_pile_spacing) + .boxed(), + ) + }) + .collect() } fn render_current_user( &self, workspace: &ViewHandle, theme: &Theme, + user: &Arc, + peer_id: PeerId, cx: &mut RenderContext, - ) -> Option { - let user = workspace.read(cx).user_store().read(cx).current_user(); + ) -> ElementBox { let replica_id = workspace.read(cx).project().read(cx).replica_id(); - let status = *workspace.read(cx).client().status().borrow(); - if let Some(user) = user { - Some(self.render_avatar(&user, Some(replica_id), None, workspace, theme, cx)) - } else if matches!(status, client::Status::UpgradeRequired) { - None - } else { - Some( - MouseEventHandler::::new(0, cx, |state, _| { - let style = theme - .workspace - .titlebar - .sign_in_prompt - .style_for(state, false); - Label::new("Sign in", style.text.clone()) - .contained() - .with_style(style.container) - .boxed() - }) - .on_click(MouseButton::Left, |_, cx| cx.dispatch_action(Authenticate)) - .with_cursor_style(CursorStyle::PointingHand) - .aligned() - .boxed(), - ) - } + Container::new(self.render_face_pile( + user, + Some(replica_id), + peer_id, + None, + workspace, + theme, + cx, + )) + .with_margin_right(theme.workspace.titlebar.item_spacing) + .boxed() } - fn render_avatar( + fn render_face_pile( &self, user: &User, replica_id: Option, - peer: Option<(PeerId, &str, ParticipantLocation)>, + peer_id: PeerId, + location: Option, workspace: &ViewHandle, theme: &Theme, cx: &mut RenderContext, ) -> ElementBox { - let is_followed = peer.map_or(false, |(peer_id, _, _)| { - workspace.read(cx).is_following(peer_id) - }); + let project_id = workspace.read(cx).project().read(cx).remote_id(); + let room = ActiveCall::global(cx).read(cx).room(); + let is_being_followed = workspace.read(cx).is_being_followed(peer_id); + let followed_by_self = room + .and_then(|room| { + Some( + is_being_followed + && room + .read(cx) + .followers_for(peer_id, project_id?) + .iter() + .any(|&follower| { + Some(follower) == workspace.read(cx).client().peer_id() + }), + ) + }) + .unwrap_or(false); - let mut avatar_style; - if let Some((_, _, location)) = peer.as_ref() { - if let ParticipantLocation::SharedProject { project_id } = *location { - if Some(project_id) == workspace.read(cx).project().read(cx).remote_id() { - avatar_style = theme.workspace.titlebar.avatar; - } else { - avatar_style = theme.workspace.titlebar.inactive_avatar; - } - } else { - avatar_style = theme.workspace.titlebar.inactive_avatar; - } - } else { - avatar_style = theme.workspace.titlebar.avatar; - } + let leader_style = theme.workspace.titlebar.leader_avatar; + let follower_style = theme.workspace.titlebar.follower_avatar; - let mut replica_color = None; + let mut background_color = theme + .workspace + .titlebar + .container + .background_color + .unwrap_or_default(); if let Some(replica_id) = replica_id { - let color = theme.editor.replica_selection_style(replica_id).cursor; - replica_color = Some(color); - if is_followed { - avatar_style.border = Border::all(1.0, color); + if followed_by_self { + let selection = theme.editor.replica_selection_style(replica_id).selection; + background_color = Color::blend(selection, background_color); + background_color.a = 255; } } - let content = Stack::new() + let mut content = Stack::new() .with_children(user.avatar.as_ref().map(|avatar| { - Image::new(avatar.clone()) - .with_style(avatar_style) - .constrained() - .with_width(theme.workspace.titlebar.avatar_width) - .aligned() - .boxed() - })) - .with_children(replica_color.map(|replica_color| { - AvatarRibbon::new(replica_color) - .constrained() - .with_width(theme.workspace.titlebar.avatar_ribbon.width) - .with_height(theme.workspace.titlebar.avatar_ribbon.height) - .aligned() - .bottom() - .boxed() + let face_pile = FacePile::new(theme.workspace.titlebar.follower_avatar_overlap) + .with_child(Self::render_face( + avatar.clone(), + Self::location_style(workspace, location, leader_style, cx), + background_color, + )) + .with_children( + (|| { + let project_id = project_id?; + let room = room?.read(cx); + let followers = room.followers_for(peer_id, project_id); + + Some(followers.into_iter().flat_map(|&follower| { + let remote_participant = + room.remote_participant_for_peer_id(follower); + + let avatar = remote_participant + .and_then(|p| p.user.avatar.clone()) + .or_else(|| { + if follower == workspace.read(cx).client().peer_id()? { + workspace + .read(cx) + .user_store() + .read(cx) + .current_user()? + .avatar + .clone() + } else { + None + } + })?; + + let location = remote_participant.map(|p| p.location); + + Some(Self::render_face( + avatar.clone(), + Self::location_style(workspace, location, follower_style, cx), + background_color, + )) + })) + })() + .into_iter() + .flatten(), + ); + + let mut container = face_pile + .contained() + .with_style(theme.workspace.titlebar.leader_selection); + + if let Some(replica_id) = replica_id { + if followed_by_self { + let color = theme.editor.replica_selection_style(replica_id).selection; + container = container.with_background_color(color); + } + } + + container.boxed() })) - .constrained() - .with_width(theme.workspace.titlebar.avatar_width) - .contained() - .with_margin_left(theme.workspace.titlebar.avatar_margin) + .with_children((|| { + let replica_id = replica_id?; + let color = theme.editor.replica_selection_style(replica_id).cursor; + Some( + AvatarRibbon::new(color) + .constrained() + .with_width(theme.workspace.titlebar.avatar_ribbon.width) + .with_height(theme.workspace.titlebar.avatar_ribbon.height) + .aligned() + .bottom() + .boxed(), + ) + })()) .boxed(); - if let Some((peer_id, peer_github_login, location)) = peer { + if let Some(location) = location { if let Some(replica_id) = replica_id { - MouseEventHandler::::new(replica_id.into(), cx, move |_, _| content) + content = + MouseEventHandler::::new(replica_id.into(), cx, move |_, _| { + content + }) .with_cursor_style(CursorStyle::PointingHand) .on_click(MouseButton::Left, move |_, cx| { cx.dispatch_action(ToggleFollow(peer_id)) }) .with_tooltip::( peer_id.as_u64() as usize, - if is_followed { - format!("Unfollow {}", peer_github_login) + if is_being_followed { + format!("Unfollow {}", user.github_login) } else { - format!("Follow {}", peer_github_login) + format!("Follow {}", user.github_login) }, Some(Box::new(FollowNextCollaborator)), theme.tooltip.clone(), cx, ) - .boxed() + .boxed(); } else if let ParticipantLocation::SharedProject { project_id } = location { let user_id = user.id; - MouseEventHandler::::new(peer_id.as_u64() as usize, cx, move |_, _| { - content - }) + content = MouseEventHandler::::new( + peer_id.as_u64() as usize, + cx, + move |_, _| content, + ) .with_cursor_style(CursorStyle::PointingHand) .on_click(MouseButton::Left, move |_, cx| { cx.dispatch_action(JoinProject { @@ -485,29 +788,63 @@ impl CollabTitlebarItem { }) .with_tooltip::( peer_id.as_u64() as usize, - format!("Follow {} into external project", peer_github_login), + format!("Follow {} into external project", user.github_login), Some(Box::new(FollowNextCollaborator)), theme.tooltip.clone(), cx, ) - .boxed() + .boxed(); + } + } + content + } + + fn location_style( + workspace: &ViewHandle, + location: Option, + mut style: AvatarStyle, + cx: &RenderContext, + ) -> AvatarStyle { + if let Some(location) = location { + if let ParticipantLocation::SharedProject { project_id } = location { + if Some(project_id) != workspace.read(cx).project().read(cx).remote_id() { + style.image.grayscale = true; + } } else { - content + style.image.grayscale = true; } - } else { - content } + + style + } + + fn render_face( + avatar: Arc, + avatar_style: AvatarStyle, + background_color: Color, + ) -> ElementBox { + Image::new(avatar) + .with_style(avatar_style.image) + .aligned() + .contained() + .with_background_color(background_color) + .with_corner_radius(avatar_style.outer_corner_radius) + .constrained() + .with_width(avatar_style.outer_width) + .with_height(avatar_style.outer_width) + .aligned() + .boxed() } fn render_connection_status( &self, - workspace: &ViewHandle, + status: &client::Status, cx: &mut RenderContext, ) -> Option { enum ConnectionStatusButton {} let theme = &cx.global::().theme.clone(); - match &*workspace.read(cx).client().status().borrow() { + match status { client::Status::ConnectionError | client::Status::ConnectionLost | client::Status::Reauthenticating { .. } diff --git a/crates/collab_ui/src/collab_ui.rs b/crates/collab_ui/src/collab_ui.rs index d26e2c99ccfb53d9b5e83160464df1a5c73aeafe..6abfec21f74f0918e6338bc77a2e8aa8acfe783b 100644 --- a/crates/collab_ui/src/collab_ui.rs +++ b/crates/collab_ui/src/collab_ui.rs @@ -1,8 +1,10 @@ mod collab_titlebar_item; +mod collaborator_list_popover; mod contact_finder; mod contact_list; mod contact_notification; mod contacts_popover; +mod face_pile; mod incoming_call_notification; mod notifications; mod project_shared_notification; @@ -10,7 +12,7 @@ mod sharing_status_indicator; use anyhow::anyhow; use call::ActiveCall; -pub use collab_titlebar_item::{CollabTitlebarItem, ToggleCollaborationMenu}; +pub use collab_titlebar_item::{CollabTitlebarItem, ToggleContactsMenu}; use gpui::{actions, MutableAppContext, Task}; use std::sync::Arc; use workspace::{AppState, JoinProject, ToggleFollow, Workspace}; @@ -116,7 +118,7 @@ fn join_project(action: &JoinProject, app_state: Arc, cx: &mut Mutable }); if let Some(follow_peer_id) = follow_peer_id { - if !workspace.is_following(follow_peer_id) { + if !workspace.is_being_followed(follow_peer_id) { workspace .toggle_follow(&ToggleFollow(follow_peer_id), cx) .map(|follow| follow.detach_and_log_err(cx)); diff --git a/crates/collab_ui/src/collaborator_list_popover.rs b/crates/collab_ui/src/collaborator_list_popover.rs new file mode 100644 index 0000000000000000000000000000000000000000..e6bebf861b596d8b4518c7a5c2f4fb1595266e4b --- /dev/null +++ b/crates/collab_ui/src/collaborator_list_popover.rs @@ -0,0 +1,165 @@ +use call::ActiveCall; +use client::UserStore; +use gpui::Action; +use gpui::{ + actions, elements::*, Entity, ModelHandle, MouseButton, RenderContext, View, ViewContext, +}; +use settings::Settings; + +use crate::collab_titlebar_item::ToggleCollaboratorList; + +pub(crate) enum Event { + Dismissed, +} + +enum Collaborator { + SelfUser { username: String }, + RemoteUser { username: String }, +} + +actions!(collaborator_list_popover, [NoOp]); + +pub(crate) struct CollaboratorListPopover { + list_state: ListState, +} + +impl Entity for CollaboratorListPopover { + type Event = Event; +} + +impl View for CollaboratorListPopover { + fn ui_name() -> &'static str { + "CollaboratorListPopover" + } + + fn render(&mut self, cx: &mut RenderContext) -> ElementBox { + let theme = cx.global::().theme.clone(); + + MouseEventHandler::::new(0, cx, |_, _| { + List::new(self.list_state.clone()) + .contained() + .with_style(theme.contacts_popover.container) //TODO: Change the name of this theme key + .constrained() + .with_width(theme.contacts_popover.width) + .with_height(theme.contacts_popover.height) + .boxed() + }) + .on_down_out(MouseButton::Left, move |_, cx| { + cx.dispatch_action(ToggleCollaboratorList); + }) + .boxed() + } + + fn focus_out(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext) { + cx.emit(Event::Dismissed); + } +} + +impl CollaboratorListPopover { + pub fn new(user_store: ModelHandle, cx: &mut ViewContext) -> Self { + let active_call = ActiveCall::global(cx); + + let mut collaborators = user_store + .read(cx) + .current_user() + .map(|u| Collaborator::SelfUser { + username: u.github_login.clone(), + }) + .into_iter() + .collect::>(); + + //TODO: What should the canonical sort here look like, consult contacts list implementation + if let Some(room) = active_call.read(cx).room() { + for participant in room.read(cx).remote_participants() { + collaborators.push(Collaborator::RemoteUser { + username: participant.1.user.github_login.clone(), + }); + } + } + + Self { + list_state: ListState::new( + collaborators.len(), + Orientation::Top, + 0., + cx, + move |_, index, cx| match &collaborators[index] { + Collaborator::SelfUser { username } => render_collaborator_list_entry( + index, + username, + None::, + None, + Svg::new("icons/chevron_right_12.svg"), + NoOp, + "Leave call".to_owned(), + cx, + ), + + Collaborator::RemoteUser { username } => render_collaborator_list_entry( + index, + username, + Some(NoOp), + Some(format!("Follow {username}")), + Svg::new("icons/x_mark_12.svg"), + NoOp, + format!("Remove {username} from call"), + cx, + ), + }, + ), + } + } +} + +fn render_collaborator_list_entry( + index: usize, + username: &str, + username_action: Option, + username_tooltip: Option, + icon: Svg, + icon_action: IA, + icon_tooltip: String, + cx: &mut RenderContext, +) -> ElementBox { + enum Username {} + enum UsernameTooltip {} + enum Icon {} + enum IconTooltip {} + + let theme = &cx.global::().theme; + let username_theme = theme.contact_list.contact_username.text.clone(); + let tooltip_theme = theme.tooltip.clone(); + + let username = MouseEventHandler::::new(index, cx, |_, _| { + Label::new(username.to_owned(), username_theme.clone()).boxed() + }) + .on_click(MouseButton::Left, move |_, cx| { + if let Some(username_action) = username_action.clone() { + cx.dispatch_action(username_action); + } + }); + + Flex::row() + .with_child(if let Some(username_tooltip) = username_tooltip { + username + .with_tooltip::( + index, + username_tooltip, + None, + tooltip_theme.clone(), + cx, + ) + .boxed() + } else { + username.boxed() + }) + .with_child( + MouseEventHandler::::new(index, cx, |_, _| icon.boxed()) + .on_click(MouseButton::Left, move |_, cx| { + cx.dispatch_action(icon_action.clone()) + }) + .with_tooltip::(index, icon_tooltip, None, tooltip_theme, cx) + .boxed(), + ) + .boxed() +} diff --git a/crates/collab_ui/src/contact_list.rs b/crates/collab_ui/src/contact_list.rs index a1607750c902abd84ba4b7f55a97cd2fc85e4a1a..3bb036d3366a4de8f4bb8e8253ae4470ce86edbc 100644 --- a/crates/collab_ui/src/contact_list.rs +++ b/crates/collab_ui/src/contact_list.rs @@ -1,3 +1,4 @@ +use super::collab_titlebar_item::LeaveCall; use crate::contacts_popover; use call::ActiveCall; use client::{proto::PeerId, Contact, User, UserStore}; @@ -18,22 +19,20 @@ use serde::Deserialize; use settings::Settings; use std::{mem, sync::Arc}; use theme::IconButton; -use util::ResultExt; use workspace::{JoinProject, OpenSharedScreen}; impl_actions!(contact_list, [RemoveContact, RespondToContactRequest]); -impl_internal_actions!(contact_list, [ToggleExpanded, Call, LeaveCall]); +impl_internal_actions!(contact_list, [ToggleExpanded, Call]); pub fn init(cx: &mut MutableAppContext) { cx.add_action(ContactList::remove_contact); cx.add_action(ContactList::respond_to_contact_request); - cx.add_action(ContactList::clear_filter); + cx.add_action(ContactList::cancel); cx.add_action(ContactList::select_next); cx.add_action(ContactList::select_prev); cx.add_action(ContactList::confirm); cx.add_action(ContactList::toggle_expanded); cx.add_action(ContactList::call); - cx.add_action(ContactList::leave_call); } #[derive(Clone, PartialEq)] @@ -45,9 +44,6 @@ struct Call { initial_project: Option>, } -#[derive(Copy, Clone, PartialEq)] -struct LeaveCall; - #[derive(Clone, Copy, PartialEq, Eq, Debug, PartialOrd, Ord)] enum Section { ActiveCall, @@ -326,7 +322,7 @@ impl ContactList { .detach(); } - fn clear_filter(&mut self, _: &Cancel, cx: &mut ViewContext) { + fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext) { let did_clear = self.filter_editor.update(cx, |editor, cx| { if editor.buffer().read(cx).len(cx) > 0 { editor.set_text("", cx); @@ -335,6 +331,7 @@ impl ContactList { false } }); + if !did_clear { cx.emit(Event::Dismissed); } @@ -980,6 +977,7 @@ impl ContactList { cx: &mut RenderContext, ) -> ElementBox { enum Header {} + enum LeaveCallContactList {} let header_style = theme .header_row @@ -992,9 +990,9 @@ impl ContactList { }; let leave_call = if section == Section::ActiveCall { Some( - MouseEventHandler::::new(0, cx, |state, _| { + MouseEventHandler::::new(0, cx, |state, _| { let style = theme.leave_call.style_for(state, false); - Label::new("Leave Session", style.text.clone()) + Label::new("Leave Call", style.text.clone()) .contained() .with_style(style.container) .boxed() @@ -1283,12 +1281,6 @@ impl ContactList { }) .detach_and_log_err(cx); } - - fn leave_call(&mut self, _: &LeaveCall, cx: &mut ViewContext) { - ActiveCall::global(cx) - .update(cx, |call, cx| call.hang_up(cx)) - .log_err(); - } } impl Entity for ContactList { @@ -1302,7 +1294,7 @@ impl View for ContactList { fn keymap_context(&self, _: &AppContext) -> KeymapContext { let mut cx = Self::default_keymap_context(); - cx.set.insert("menu".into()); + cx.add_identifier("menu"); cx } @@ -1334,7 +1326,7 @@ impl View for ContactList { }) .with_tooltip::( 0, - "Add contact".into(), + "Search for new contact".into(), None, theme.tooltip.clone(), cx, diff --git a/crates/collab_ui/src/contacts_popover.rs b/crates/collab_ui/src/contacts_popover.rs index 37280f929e7c9a5536588a4543d26f8fb8214282..0c67ef4c7c094a17758284bc5b2e8aeac8d30586 100644 --- a/crates/collab_ui/src/contacts_popover.rs +++ b/crates/collab_ui/src/contacts_popover.rs @@ -1,4 +1,4 @@ -use crate::{contact_finder::ContactFinder, contact_list::ContactList, ToggleCollaborationMenu}; +use crate::{contact_finder::ContactFinder, contact_list::ContactList, ToggleContactsMenu}; use client::UserStore; use gpui::{ actions, elements::*, ClipboardItem, CursorStyle, Entity, ModelHandle, MouseButton, @@ -155,7 +155,7 @@ impl View for ContactsPopover { .boxed() }) .on_down_out(MouseButton::Left, move |_, cx| { - cx.dispatch_action(ToggleCollaborationMenu); + cx.dispatch_action(ToggleContactsMenu); }) .boxed() } diff --git a/crates/collab_ui/src/face_pile.rs b/crates/collab_ui/src/face_pile.rs new file mode 100644 index 0000000000000000000000000000000000000000..3b95443fee822a32b79c15617e13689aa8f6fac4 --- /dev/null +++ b/crates/collab_ui/src/face_pile.rs @@ -0,0 +1,101 @@ +use std::ops::Range; + +use gpui::{ + geometry::{ + rect::RectF, + vector::{vec2f, Vector2F}, + }, + json::ToJson, + serde_json::{self, json}, + Axis, DebugContext, Element, ElementBox, MeasurementContext, PaintContext, +}; + +pub(crate) struct FacePile { + overlap: f32, + faces: Vec, +} + +impl FacePile { + pub fn new(overlap: f32) -> FacePile { + FacePile { + overlap, + faces: Vec::new(), + } + } +} + +impl Element for FacePile { + type LayoutState = (); + type PaintState = (); + + fn layout( + &mut self, + constraint: gpui::SizeConstraint, + cx: &mut gpui::LayoutContext, + ) -> (Vector2F, Self::LayoutState) { + debug_assert!(constraint.max_along(Axis::Horizontal) == f32::INFINITY); + + let mut width = 0.; + for face in &mut self.faces { + width += face.layout(constraint, cx).x(); + } + width -= self.overlap * self.faces.len().saturating_sub(1) as f32; + + (Vector2F::new(width, constraint.max.y()), ()) + } + + fn paint( + &mut self, + bounds: RectF, + visible_bounds: RectF, + _layout: &mut Self::LayoutState, + cx: &mut PaintContext, + ) -> Self::PaintState { + let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); + + let origin_y = bounds.upper_right().y(); + let mut origin_x = bounds.upper_right().x(); + + for face in self.faces.iter_mut().rev() { + let size = face.size(); + origin_x -= size.x(); + cx.paint_layer(None, |cx| { + face.paint(vec2f(origin_x, origin_y), visible_bounds, cx); + }); + origin_x += self.overlap; + } + + () + } + + fn rect_for_text_range( + &self, + _: Range, + _: RectF, + _: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + _: &MeasurementContext, + ) -> Option { + None + } + + fn debug( + &self, + bounds: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + _: &DebugContext, + ) -> serde_json::Value { + json!({ + "type": "FacePile", + "bounds": bounds.to_json() + }) + } +} + +impl Extend for FacePile { + fn extend>(&mut self, children: T) { + self.faces.extend(children); + } +} diff --git a/crates/context_menu/src/context_menu.rs b/crates/context_menu/src/context_menu.rs index 6d5a5cb549d3e39702f2e9f4e6e4a926e60cea1d..e1b9f81c1a6342a1f23377bc16f8cf985d96f2a3 100644 --- a/crates/context_menu/src/context_menu.rs +++ b/crates/context_menu/src/context_menu.rs @@ -5,7 +5,9 @@ use gpui::{ }; use menu::*; use settings::Settings; -use std::{any::TypeId, time::Duration}; +use std::{any::TypeId, borrow::Cow, time::Duration}; + +pub type StaticItem = Box ElementBox>; #[derive(Copy, Clone, PartialEq)] struct Clicked; @@ -24,16 +26,17 @@ pub fn init(cx: &mut MutableAppContext) { pub enum ContextMenuItem { Item { - label: String, + label: Cow<'static, str>, action: Box, }, + Static(StaticItem), Separator, } impl ContextMenuItem { - pub fn item(label: impl ToString, action: impl 'static + Action) -> Self { + pub fn item(label: impl Into>, action: impl 'static + Action) -> Self { Self::Item { - label: label.to_string(), + label: label.into(), action: Box::new(action), } } @@ -42,14 +45,14 @@ impl ContextMenuItem { Self::Separator } - fn is_separator(&self) -> bool { - matches!(self, Self::Separator) + fn is_action(&self) -> bool { + matches!(self, Self::Item { .. }) } fn action_id(&self) -> Option { match self { ContextMenuItem::Item { action, .. } => Some(action.id()), - ContextMenuItem::Separator => None, + ContextMenuItem::Static(..) | ContextMenuItem::Separator => None, } } } @@ -58,6 +61,7 @@ pub struct ContextMenu { show_count: usize, anchor_position: Vector2F, anchor_corner: AnchorCorner, + position_mode: OverlayPositionMode, items: Vec, selected_index: Option, visible: bool, @@ -78,7 +82,7 @@ impl View for ContextMenu { fn keymap_context(&self, _: &AppContext) -> KeymapContext { let mut cx = Self::default_keymap_context(); - cx.set.insert("menu".into()); + cx.add_identifier("menu"); cx } @@ -105,6 +109,7 @@ impl View for ContextMenu { .with_fit_mode(OverlayFitMode::SnapToWindow) .with_anchor_position(self.anchor_position) .with_anchor_corner(self.anchor_corner) + .with_position_mode(self.position_mode) .boxed() } @@ -121,6 +126,7 @@ impl ContextMenu { show_count: 0, anchor_position: Default::default(), anchor_corner: AnchorCorner::TopLeft, + position_mode: OverlayPositionMode::Window, items: Default::default(), selected_index: Default::default(), visible: Default::default(), @@ -188,13 +194,13 @@ impl ContextMenu { } fn select_first(&mut self, _: &SelectFirst, cx: &mut ViewContext) { - self.selected_index = self.items.iter().position(|item| !item.is_separator()); + self.selected_index = self.items.iter().position(|item| item.is_action()); cx.notify(); } fn select_last(&mut self, _: &SelectLast, cx: &mut ViewContext) { for (ix, item) in self.items.iter().enumerate().rev() { - if !item.is_separator() { + if item.is_action() { self.selected_index = Some(ix); cx.notify(); break; @@ -205,7 +211,7 @@ impl ContextMenu { fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext) { if let Some(ix) = self.selected_index { for (ix, item) in self.items.iter().enumerate().skip(ix + 1) { - if !item.is_separator() { + if item.is_action() { self.selected_index = Some(ix); cx.notify(); break; @@ -219,7 +225,7 @@ impl ContextMenu { fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext) { if let Some(ix) = self.selected_index { for (ix, item) in self.items.iter().enumerate().take(ix).rev() { - if !item.is_separator() { + if item.is_action() { self.selected_index = Some(ix); cx.notify(); break; @@ -234,7 +240,7 @@ impl ContextMenu { &mut self, anchor_position: Vector2F, anchor_corner: AnchorCorner, - items: impl IntoIterator, + items: Vec, cx: &mut ViewContext, ) { let mut items = items.into_iter().peekable(); @@ -254,6 +260,10 @@ impl ContextMenu { cx.notify(); } + pub fn set_position_mode(&mut self, mode: OverlayPositionMode) { + self.position_mode = mode; + } + fn render_menu_for_measurement(&self, cx: &mut RenderContext) -> impl Element { let window_id = cx.window_id(); let style = cx.global::().theme.context_menu.clone(); @@ -273,6 +283,9 @@ impl ContextMenu { .with_style(style.container) .boxed() } + + ContextMenuItem::Static(f) => f(cx), + ContextMenuItem::Separator => Empty::new() .collapsed() .contained() @@ -302,6 +315,9 @@ impl ContextMenu { ) .boxed() } + + ContextMenuItem::Static(_) => Empty::new().boxed(), + ContextMenuItem::Separator => Empty::new() .collapsed() .constrained() @@ -339,7 +355,7 @@ impl ContextMenu { Flex::row() .with_child( - Label::new(label.to_string(), style.label.clone()) + Label::new(label.clone(), style.label.clone()) .contained() .boxed(), ) @@ -366,6 +382,9 @@ impl ContextMenu { .on_drag(MouseButton::Left, |_, _| {}) .boxed() } + + ContextMenuItem::Static(f) => f(cx), + ContextMenuItem::Separator => Empty::new() .constrained() .with_height(1.) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 9aaaf765171f7a16a87976f36ebbd3ba4806afce..2cc26c26edcfdd8988651bb4494327e4f7e07d9a 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -5071,7 +5071,7 @@ impl Editor { GotoDefinitionKind::Type => project.type_definition(&buffer, head, cx), }); - cx.spawn(|workspace, mut cx| async move { + cx.spawn_labeled("Fetching Definition...", |workspace, mut cx| async move { let definitions = definitions.await?; workspace.update(&mut cx, |workspace, cx| { Editor::navigate_to_definitions(workspace, editor_handle, definitions, cx); @@ -5151,31 +5151,36 @@ impl Editor { let project = workspace.project().clone(); let references = project.update(cx, |project, cx| project.references(&buffer, head, cx)); - Some(cx.spawn(|workspace, mut cx| async move { - let locations = references.await?; - if locations.is_empty() { - return Ok(()); - } + Some(cx.spawn_labeled( + "Finding All References...", + |workspace, mut cx| async move { + let locations = references.await?; + if locations.is_empty() { + return Ok(()); + } - workspace.update(&mut cx, |workspace, cx| { - let title = locations - .first() - .as_ref() - .map(|location| { - let buffer = location.buffer.read(cx); - format!( - "References to `{}`", - buffer - .text_for_range(location.range.clone()) - .collect::() - ) - }) - .unwrap(); - Self::open_locations_in_multibuffer(workspace, locations, replica_id, title, cx); - }); + workspace.update(&mut cx, |workspace, cx| { + let title = locations + .first() + .as_ref() + .map(|location| { + let buffer = location.buffer.read(cx); + format!( + "References to `{}`", + buffer + .text_for_range(location.range.clone()) + .collect::() + ) + }) + .unwrap(); + Self::open_locations_in_multibuffer( + workspace, locations, replica_id, title, cx, + ); + }); - Ok(()) - })) + Ok(()) + }, + )) } /// Opens a multibuffer with the given project locations in it @@ -5454,21 +5459,20 @@ impl Editor { None => return None, }; - Some(self.perform_format(project, cx)) + Some(self.perform_format(project, FormatTrigger::Manual, cx)) } fn perform_format( &mut self, project: ModelHandle, + trigger: FormatTrigger, cx: &mut ViewContext<'_, Self>, ) -> Task> { let buffer = self.buffer().clone(); let buffers = buffer.read(cx).all_buffers(); let mut timeout = cx.background().timer(FORMAT_TIMEOUT).fuse(); - let format = project.update(cx, |project, cx| { - project.format(buffers, true, FormatTrigger::Manual, cx) - }); + let format = project.update(cx, |project, cx| project.format(buffers, true, trigger, cx)); cx.spawn(|_, mut cx| async move { let transaction = futures::select_biased! { @@ -6428,17 +6432,13 @@ impl View for Editor { EditorMode::AutoHeight { .. } => "auto_height", EditorMode::Full => "full", }; - context.map.insert("mode".into(), mode.into()); + context.add_key("mode", mode); if self.pending_rename.is_some() { - context.set.insert("renaming".into()); + context.add_identifier("renaming"); } match self.context_menu.as_ref() { - Some(ContextMenu::Completions(_)) => { - context.set.insert("showing_completions".into()); - } - Some(ContextMenu::CodeActions(_)) => { - context.set.insert("showing_code_actions".into()); - } + Some(ContextMenu::Completions(_)) => context.add_identifier("showing_completions"), + Some(ContextMenu::CodeActions(_)) => context.add_identifier("showing_code_actions"), None => {} } diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 94699a8d5a3da3939e339e6824c050a81acccd01..b0ce4a68b909b9bf56abc75582bb4d471f2f7e97 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -4193,7 +4193,9 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) { let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx)); editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx)); - let format = editor.update(cx, |editor, cx| editor.perform_format(project.clone(), cx)); + let format = editor.update(cx, |editor, cx| { + editor.perform_format(project.clone(), FormatTrigger::Manual, cx) + }); fake_server .handle_request::(move |params, _| async move { assert_eq!( @@ -4225,7 +4227,9 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) { futures::future::pending::<()>().await; unreachable!() }); - let format = editor.update(cx, |editor, cx| editor.perform_format(project, cx)); + let format = editor.update(cx, |editor, cx| { + editor.perform_format(project, FormatTrigger::Manual, cx) + }); cx.foreground().advance_clock(super::FORMAT_TIMEOUT); cx.foreground().start_waiting(); format.await.unwrap(); diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 47963597f6bc83fb523d509348c41a515b09a4b6..b0b368acd646580a5251808d2e4d1634fcbfa570 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -14,7 +14,7 @@ use language::{ proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, OffsetRangeExt, Point, SelectionGoal, }; -use project::{Item as _, Project, ProjectPath}; +use project::{FormatTrigger, Item as _, Project, ProjectPath}; use rpc::proto::{self, update_view}; use settings::Settings; use smallvec::SmallVec; @@ -608,7 +608,7 @@ impl Item for Editor { cx: &mut ViewContext, ) -> Task> { self.report_event("save editor", cx); - let format = self.perform_format(project.clone(), cx); + let format = self.perform_format(project.clone(), FormatTrigger::Save, cx); let buffers = self.buffer().clone().read(cx).all_buffers(); cx.as_mut().spawn(|mut cx| async move { format.await?; diff --git a/crates/editor/src/test/editor_lsp_test_context.rs b/crates/editor/src/test/editor_lsp_test_context.rs index 345709abf33f89862579cedd8482318063d0da6a..1b6d846e710f4ebd6f84c8053ab88d4c36294ef5 100644 --- a/crates/editor/src/test/editor_lsp_test_context.rs +++ b/crates/editor/src/test/editor_lsp_test_context.rs @@ -39,7 +39,7 @@ impl<'a> EditorLspTestContext<'a> { pane::init(cx); }); - let params = cx.update(AppState::test); + let app_state = cx.update(AppState::test); let file_name = format!( "file.{}", @@ -56,10 +56,10 @@ impl<'a> EditorLspTestContext<'a> { })) .await; - let project = Project::test(params.fs.clone(), [], cx).await; + let project = Project::test(app_state.fs.clone(), [], cx).await; project.update(cx, |project, _| project.languages().add(Arc::new(language))); - params + app_state .fs .as_fake() .insert_tree("/root", json!({ "dir": { file_name.clone(): "" }})) diff --git a/crates/feedback/src/feedback_editor.rs b/crates/feedback/src/feedback_editor.rs index 656b3282e00145e2d49c6356a939bb6f26640e30..e30b85170c5fd7f128948d7622c166b03b30522d 100644 --- a/crates/feedback/src/feedback_editor.rs +++ b/crates/feedback/src/feedback_editor.rs @@ -13,7 +13,6 @@ use gpui::{ elements::{ChildView, Flex, Label, ParentElement}, serde_json, AnyViewHandle, AppContext, Element, ElementBox, Entity, ModelHandle, MutableAppContext, PromptLevel, RenderContext, Task, View, ViewContext, ViewHandle, - WeakViewHandle, }; use isahc::Request; use language::Buffer; @@ -24,7 +23,6 @@ use serde::Serialize; use workspace::{ item::{Item, ItemHandle}, searchable::{SearchableItem, SearchableItemHandle}, - smallvec::SmallVec, AppState, Workspace, }; @@ -259,16 +257,10 @@ impl Item for FeedbackEditor { self.editor.for_each_project_item(cx, f) } - fn to_item_events(_: &Self::Event) -> SmallVec<[workspace::item::ItemEvent; 2]> { - SmallVec::new() - } - fn is_singleton(&self, _: &AppContext) -> bool { true } - fn set_nav_history(&mut self, _: workspace::ItemNavHistory, _: &mut ViewContext) {} - fn can_save(&self, _: &AppContext) -> bool { true } @@ -295,7 +287,7 @@ impl Item for FeedbackEditor { _: ModelHandle, _: &mut ViewContext, ) -> Task> { - unreachable!("reload should not have been called") + Task::Ready(Some(Ok(()))) } fn clone_on_split( @@ -322,20 +314,6 @@ impl Item for FeedbackEditor { )) } - fn serialized_item_kind() -> Option<&'static str> { - None - } - - fn deserialize( - _: ModelHandle, - _: WeakViewHandle, - _: workspace::WorkspaceId, - _: workspace::ItemId, - _: &mut ViewContext, - ) -> Task>> { - unreachable!() - } - fn as_searchable(&self, handle: &ViewHandle) -> Option> { Some(Box::new(handle.clone())) } diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 7561a68222c4d30133659cf4ae0901a1f0ea1ffa..273440dce253f54e8ccf5cb520bff321d403b1be 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -23,6 +23,7 @@ pub struct FileFinder { latest_search_id: usize, latest_search_did_cancel: bool, latest_search_query: String, + relative_to: Option>, matches: Vec, selected: Option<(usize, Arc)>, cancel_flag: Arc, @@ -90,7 +91,11 @@ impl FileFinder { fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext) { workspace.toggle_modal(cx, |workspace, cx| { let project = workspace.project().clone(); - let finder = cx.add_view(|cx| Self::new(project, cx)); + let relative_to = workspace + .active_item(cx) + .and_then(|item| item.project_path(cx)) + .map(|project_path| project_path.path.clone()); + let finder = cx.add_view(|cx| Self::new(project, relative_to, cx)); cx.subscribe(&finder, Self::on_event).detach(); finder }); @@ -115,7 +120,11 @@ impl FileFinder { } } - pub fn new(project: ModelHandle, cx: &mut ViewContext) -> Self { + pub fn new( + project: ModelHandle, + relative_to: Option>, + cx: &mut ViewContext, + ) -> Self { let handle = cx.weak_handle(); cx.observe(&project, Self::project_updated).detach(); Self { @@ -125,6 +134,7 @@ impl FileFinder { latest_search_id: 0, latest_search_did_cancel: false, latest_search_query: String::new(), + relative_to, matches: Vec::new(), selected: None, cancel_flag: Arc::new(AtomicBool::new(false)), @@ -137,6 +147,7 @@ impl FileFinder { } fn spawn_search(&mut self, query: String, cx: &mut ViewContext) -> Task<()> { + let relative_to = self.relative_to.clone(); let worktrees = self .project .read(cx) @@ -165,6 +176,7 @@ impl FileFinder { let matches = fuzzy::match_path_sets( candidate_sets.as_slice(), &query, + relative_to, false, 100, &cancel_flag, @@ -377,7 +389,7 @@ mod tests { Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx) }); let (_, finder) = - cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), cx)); + cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx)); let query = "hi".to_string(); finder @@ -453,7 +465,7 @@ mod tests { Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx) }); let (_, finder) = - cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), cx)); + cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx)); finder .update(cx, |f, cx| f.spawn_search("hi".into(), cx)) .await; @@ -479,7 +491,7 @@ mod tests { Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx) }); let (_, finder) = - cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), cx)); + cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx)); // Even though there is only one worktree, that worktree's filename // is included in the matching, because the worktree is a single file. @@ -532,8 +544,9 @@ mod tests { let (_, workspace) = cx.add_window(|cx| { Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx) }); + let (_, finder) = - cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), cx)); + cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx)); // Run a search that matches two files with the same relative path. finder @@ -551,6 +564,48 @@ mod tests { }); } + #[gpui::test] + async fn test_path_distance_ordering(cx: &mut gpui::TestAppContext) { + cx.foreground().forbid_parking(); + + let app_state = cx.update(AppState::test); + app_state + .fs + .as_fake() + .insert_tree( + "/root", + json!({ + "dir1": { "a.txt": "" }, + "dir2": { + "a.txt": "", + "b.txt": "" + } + }), + ) + .await; + + let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; + let (_, workspace) = cx.add_window(|cx| { + Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx) + }); + + // When workspace has an active item, sort items which are closer to that item + // first when they have the same name. In this case, b.txt is closer to dir2's a.txt + // so that one should be sorted earlier + let b_path = Some(Arc::from(Path::new("/root/dir2/b.txt"))); + let (_, finder) = + cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), b_path, cx)); + + finder + .update(cx, |f, cx| f.spawn_search("a.txt".into(), cx)) + .await; + + finder.read_with(cx, |f, _| { + assert_eq!(f.matches[0].path.as_ref(), Path::new("dir2/a.txt")); + assert_eq!(f.matches[1].path.as_ref(), Path::new("dir1/a.txt")); + }); + } + #[gpui::test] async fn test_search_worktree_without_files(cx: &mut gpui::TestAppContext) { let app_state = cx.update(AppState::test); @@ -573,7 +628,7 @@ mod tests { Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx) }); let (_, finder) = - cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), cx)); + cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx)); finder .update(cx, |f, cx| f.spawn_search("dir".into(), cx)) .await; diff --git a/crates/fuzzy/src/matcher.rs b/crates/fuzzy/src/matcher.rs index 51ae75bac23a981804dd4626fc9daf0cc4f50af6..dafafe40a0630ebe364f5d29963ef2475aa3c2b8 100644 --- a/crates/fuzzy/src/matcher.rs +++ b/crates/fuzzy/src/matcher.rs @@ -443,6 +443,7 @@ mod tests { positions: Vec::new(), path: candidate.path.clone(), path_prefix: "".into(), + distance_to_relative_ancestor: usize::MAX, }, ); diff --git a/crates/fuzzy/src/paths.rs b/crates/fuzzy/src/paths.rs index 8d9ec97d9b26eb6c2d6b4ce7db8268eb05aac0d7..2c5ce81b1ccd09def80c7938aa1fe8591323500b 100644 --- a/crates/fuzzy/src/paths.rs +++ b/crates/fuzzy/src/paths.rs @@ -25,6 +25,9 @@ pub struct PathMatch { pub worktree_id: usize, pub path: Arc, pub path_prefix: Arc, + /// Number of steps removed from a shared parent with the relative path + /// Used to order closer paths first in the search list + pub distance_to_relative_ancestor: usize, } pub trait PathMatchCandidateSet<'a>: Send + Sync { @@ -78,6 +81,11 @@ impl Ord for PathMatch { .partial_cmp(&other.score) .unwrap_or(Ordering::Equal) .then_with(|| self.worktree_id.cmp(&other.worktree_id)) + .then_with(|| { + other + .distance_to_relative_ancestor + .cmp(&self.distance_to_relative_ancestor) + }) .then_with(|| self.path.cmp(&other.path)) } } @@ -85,6 +93,7 @@ impl Ord for PathMatch { pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>( candidate_sets: &'a [Set], query: &str, + relative_to: Option>, smart_case: bool, max_results: usize, cancel_flag: &AtomicBool, @@ -111,6 +120,7 @@ pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>( background .scoped(|scope| { for (segment_idx, results) in segment_results.iter_mut().enumerate() { + let relative_to = relative_to.clone(); scope.spawn(async move { let segment_start = segment_idx * segment_size; let segment_end = segment_start + segment_size; @@ -149,6 +159,15 @@ pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>( positions: Vec::new(), path: candidate.path.clone(), path_prefix: candidate_set.prefix(), + distance_to_relative_ancestor: relative_to.as_ref().map_or( + usize::MAX, + |relative_to| { + distance_between_paths( + candidate.path.as_ref(), + relative_to.as_ref(), + ) + }, + ), }, ); } @@ -172,3 +191,13 @@ pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>( } results } + +/// Compute the distance from a given path to some other path +/// If there is no shared path, returns usize::MAX +fn distance_between_paths(path: &Path, relative_to: &Path) -> usize { + let mut path_components = path.components(); + let mut relative_components = relative_to.components(); + + while path_components.next() == relative_components.next() {} + path_components.count() + relative_components.count() + 1 +} diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 4bdd7755937fb161d240c12be2a2c1df28e2f73e..35c91f1ff233be1ac3fdf4d7ccdc945ef4d67674 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -31,7 +31,7 @@ use uuid::Uuid; pub use action::*; use callback_collection::CallbackCollection; -use collections::{hash_map::Entry, HashMap, HashSet, VecDeque}; +use collections::{hash_map::Entry, BTreeMap, HashMap, HashSet, VecDeque}; pub use menu::*; use platform::Event; #[cfg(any(test, feature = "test-support"))] @@ -86,7 +86,7 @@ pub trait View: Entity + Sized { } fn default_keymap_context() -> keymap_matcher::KeymapContext { let mut cx = keymap_matcher::KeymapContext::default(); - cx.set.insert(Self::ui_name().into()); + cx.add_identifier(Self::ui_name()); cx } fn debug_json(&self, _: &AppContext) -> serde_json::Value { @@ -474,6 +474,7 @@ type WindowBoundsCallback = Box>, &mut MutableAppContext) -> bool, >; +type ActiveLabeledTasksCallback = Box bool>; type DeserializeActionCallback = fn(json: &str) -> anyhow::Result>; type WindowShouldCloseSubscriptionCallback = Box bool>; @@ -503,6 +504,7 @@ pub struct MutableAppContext { window_fullscreen_observations: CallbackCollection, window_bounds_observations: CallbackCollection, keystroke_observations: CallbackCollection, + active_labeled_task_observations: CallbackCollection<(), ActiveLabeledTasksCallback>, #[allow(clippy::type_complexity)] presenters_and_platform_windows: @@ -514,6 +516,8 @@ pub struct MutableAppContext { pending_flushes: usize, flushing_effects: bool, halt_action_dispatch: bool, + next_labeled_task_id: usize, + active_labeled_tasks: BTreeMap, } impl MutableAppContext { @@ -562,6 +566,7 @@ impl MutableAppContext { window_bounds_observations: Default::default(), keystroke_observations: Default::default(), action_dispatch_observations: Default::default(), + active_labeled_task_observations: Default::default(), presenters_and_platform_windows: Default::default(), foreground, pending_effects: VecDeque::new(), @@ -570,6 +575,8 @@ impl MutableAppContext { pending_flushes: 0, flushing_effects: false, halt_action_dispatch: false, + next_labeled_task_id: 0, + active_labeled_tasks: Default::default(), } } @@ -794,6 +801,12 @@ impl MutableAppContext { window.screen().display_uuid() } + pub fn active_labeled_tasks<'a>( + &'a self, + ) -> impl DoubleEndedIterator + 'a { + self.active_labeled_tasks.values().cloned() + } + pub fn render_view(&mut self, params: RenderParams) -> Result { let window_id = params.window_id; let view_id = params.view_id; @@ -1160,6 +1173,19 @@ impl MutableAppContext { ) } + pub fn observe_active_labeled_tasks(&mut self, callback: F) -> Subscription + where + F: 'static + FnMut(&mut MutableAppContext) -> bool, + { + let subscription_id = post_inc(&mut self.next_subscription_id); + self.active_labeled_task_observations + .add_callback((), subscription_id, Box::new(callback)); + Subscription::ActiveLabeledTasksObservation( + self.active_labeled_task_observations + .subscribe((), subscription_id), + ) + } + pub fn defer(&mut self, callback: impl 'static + FnOnce(&mut MutableAppContext)) { self.pending_effects.push_back(Effect::Deferred { callback: Box::new(callback), @@ -2042,6 +2068,17 @@ impl MutableAppContext { handled_by, result, } => self.handle_keystroke_effect(window_id, keystroke, handled_by, result), + Effect::ActiveLabeledTasksChanged => { + self.handle_active_labeled_tasks_changed_effect() + } + Effect::ActiveLabeledTasksObservation { + subscription_id, + callback, + } => self.active_labeled_task_observations.add_callback( + (), + subscription_id, + callback, + ), } self.pending_notifications.clear(); self.remove_dropped_entities(); @@ -2449,26 +2486,68 @@ impl MutableAppContext { } } + fn handle_active_labeled_tasks_changed_effect(&mut self) { + self.active_labeled_task_observations + .clone() + .emit((), self, move |callback, this| { + callback(this); + true + }); + } + pub fn focus(&mut self, window_id: usize, view_id: Option) { self.pending_effects .push_back(Effect::Focus { window_id, view_id }); } - pub fn spawn(&self, f: F) -> Task + fn spawn_internal(&mut self, task_name: Option<&'static str>, f: F) -> Task where F: FnOnce(AsyncAppContext) -> Fut, Fut: 'static + Future, T: 'static, { + let label_id = task_name.map(|task_name| { + let id = post_inc(&mut self.next_labeled_task_id); + self.active_labeled_tasks.insert(id, task_name); + self.pending_effects + .push_back(Effect::ActiveLabeledTasksChanged); + id + }); + let future = f(self.to_async()); let cx = self.to_async(); self.foreground.spawn(async move { let result = future.await; - cx.0.borrow_mut().flush_effects(); + let mut cx = cx.0.borrow_mut(); + + if let Some(completed_label_id) = label_id { + cx.active_labeled_tasks.remove(&completed_label_id); + cx.pending_effects + .push_back(Effect::ActiveLabeledTasksChanged); + } + cx.flush_effects(); result }) } + pub fn spawn_labeled(&mut self, task_name: &'static str, f: F) -> Task + where + F: FnOnce(AsyncAppContext) -> Fut, + Fut: 'static + Future, + T: 'static, + { + self.spawn_internal(Some(task_name), f) + } + + pub fn spawn(&mut self, f: F) -> Task + where + F: FnOnce(AsyncAppContext) -> Fut, + Fut: 'static + Future, + T: 'static, + { + self.spawn_internal(None, f) + } + pub fn to_async(&self) -> AsyncAppContext { AsyncAppContext(self.weak_self.as_ref().unwrap().upgrade().unwrap()) } @@ -2907,6 +2986,11 @@ pub enum Effect { window_id: usize, callback: WindowShouldCloseSubscriptionCallback, }, + ActiveLabeledTasksChanged, + ActiveLabeledTasksObservation { + subscription_id: usize, + callback: ActiveLabeledTasksCallback, + }, } impl Debug for Effect { @@ -3066,6 +3150,16 @@ impl Debug for Effect { ) .field("result", result) .finish(), + Effect::ActiveLabeledTasksChanged => { + f.debug_struct("Effect::ActiveLabeledTasksChanged").finish() + } + Effect::ActiveLabeledTasksObservation { + subscription_id, + callback: _, + } => f + .debug_struct("Effect::ActiveLabeledTasksObservation") + .field("subscription_id", subscription_id) + .finish(), } } } @@ -3480,7 +3574,7 @@ impl<'a, T: Entity> ModelContext<'a, T> { WeakModelHandle::new(self.model_id) } - pub fn spawn(&self, f: F) -> Task + pub fn spawn(&mut self, f: F) -> Task where F: FnOnce(ModelHandle, AsyncAppContext) -> Fut, Fut: 'static + Future, @@ -3490,7 +3584,7 @@ impl<'a, T: Entity> ModelContext<'a, T> { self.app.spawn(|cx| f(handle, cx)) } - pub fn spawn_weak(&self, f: F) -> Task + pub fn spawn_weak(&mut self, f: F) -> Task where F: FnOnce(WeakModelHandle, AsyncAppContext) -> Fut, Fut: 'static + Future, @@ -3947,6 +4041,23 @@ impl<'a, T: View> ViewContext<'a, T> { }) } + pub fn observe_active_labeled_tasks(&mut self, mut callback: F) -> Subscription + where + F: 'static + FnMut(&mut T, &mut ViewContext), + { + let observer = self.weak_handle(); + self.app.observe_active_labeled_tasks(move |cx| { + if let Some(observer) = observer.upgrade(cx) { + observer.update(cx, |observer, cx| { + callback(observer, cx); + }); + true + } else { + false + } + }) + } + pub fn emit(&mut self, payload: T::Event) { self.app.pending_effects.push_back(Effect::Event { entity_id: self.view_id, @@ -3993,7 +4104,17 @@ impl<'a, T: View> ViewContext<'a, T> { self.app.halt_action_dispatch = false; } - pub fn spawn(&self, f: F) -> Task + pub fn spawn_labeled(&mut self, task_label: &'static str, f: F) -> Task + where + F: FnOnce(ViewHandle, AsyncAppContext) -> Fut, + Fut: 'static + Future, + S: 'static, + { + let handle = self.handle(); + self.app.spawn_labeled(task_label, |cx| f(handle, cx)) + } + + pub fn spawn(&mut self, f: F) -> Task where F: FnOnce(ViewHandle, AsyncAppContext) -> Fut, Fut: 'static + Future, @@ -4003,7 +4124,7 @@ impl<'a, T: View> ViewContext<'a, T> { self.app.spawn(|cx| f(handle, cx)) } - pub fn spawn_weak(&self, f: F) -> Task + pub fn spawn_weak(&mut self, f: F) -> Task where F: FnOnce(WeakViewHandle, AsyncAppContext) -> Fut, Fut: 'static + Future, @@ -5121,6 +5242,9 @@ pub enum Subscription { KeystrokeObservation(callback_collection::Subscription), ReleaseObservation(callback_collection::Subscription), ActionObservation(callback_collection::Subscription<(), ActionObservationCallback>), + ActiveLabeledTasksObservation( + callback_collection::Subscription<(), ActiveLabeledTasksCallback>, + ), } impl Subscription { @@ -5137,6 +5261,7 @@ impl Subscription { Subscription::KeystrokeObservation(subscription) => subscription.id(), Subscription::ReleaseObservation(subscription) => subscription.id(), Subscription::ActionObservation(subscription) => subscription.id(), + Subscription::ActiveLabeledTasksObservation(subscription) => subscription.id(), } } @@ -5153,6 +5278,7 @@ impl Subscription { Subscription::WindowBoundsObservation(subscription) => subscription.detach(), Subscription::ReleaseObservation(subscription) => subscription.detach(), Subscription::ActionObservation(subscription) => subscription.detach(), + Subscription::ActiveLabeledTasksObservation(subscription) => subscription.detach(), } } } @@ -5161,6 +5287,7 @@ impl Subscription { mod tests { use super::*; use crate::{actions, elements::*, impl_actions, MouseButton, MouseButtonEvent}; + use postage::{sink::Sink, stream::Stream}; use serde::Deserialize; use smol::future::poll_once; use std::{ @@ -6512,12 +6639,12 @@ mod tests { let mut view_1 = View::new(1); let mut view_2 = View::new(2); let mut view_3 = View::new(3); - view_1.keymap_context.set.insert("a".into()); - view_2.keymap_context.set.insert("a".into()); - view_2.keymap_context.set.insert("b".into()); - view_3.keymap_context.set.insert("a".into()); - view_3.keymap_context.set.insert("b".into()); - view_3.keymap_context.set.insert("c".into()); + view_1.keymap_context.add_identifier("a"); + view_2.keymap_context.add_identifier("a"); + view_2.keymap_context.add_identifier("b"); + view_3.keymap_context.add_identifier("a"); + view_3.keymap_context.add_identifier("b"); + view_3.keymap_context.add_identifier("c"); let (window_id, view_1) = cx.add_window(Default::default(), |_| view_1); let view_2 = cx.add_view(&view_1, |_| view_2); @@ -6776,6 +6903,26 @@ mod tests { assert_eq!(presenter.borrow().rendered_views.len(), 1); } + #[crate::test(self)] + async fn test_labeled_tasks(cx: &mut TestAppContext) { + assert_eq!(None, cx.update(|cx| cx.active_labeled_tasks().next())); + let (mut sender, mut reciever) = postage::oneshot::channel::<()>(); + let task = cx + .update(|cx| cx.spawn_labeled("Test Label", |_| async move { reciever.recv().await })); + + assert_eq!( + Some("Test Label"), + cx.update(|cx| cx.active_labeled_tasks().next()) + ); + sender + .send(()) + .await + .expect("Could not send message to complete task"); + task.await; + + assert_eq!(None, cx.update(|cx| cx.active_labeled_tasks().next())); + } + #[crate::test(self)] async fn test_window_activation(cx: &mut TestAppContext) { struct View(&'static str); diff --git a/crates/gpui/src/app/action.rs b/crates/gpui/src/app/action.rs index 538f99a586a8f3e3f59c6ea365813e9d901a2e99..0b035f12c3491401618d01693f735fe9b1b91ba8 100644 --- a/crates/gpui/src/app/action.rs +++ b/crates/gpui/src/app/action.rs @@ -16,6 +16,14 @@ pub trait Action: 'static { Self: Sized; } +impl std::fmt::Debug for dyn Action { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("dyn Action") + .field("namespace", &self.namespace()) + .field("name", &self.name()) + .finish() + } +} /// Define a set of unit struct types that all implement the `Action` trait. /// /// The first argument is a namespace that will be associated with each of diff --git a/crates/gpui/src/app/menu.rs b/crates/gpui/src/app/menu.rs index 2234bfa3911cf892792470c446e0a25c3e56af7e..5fe307a7c10f0bc3a6a9f64c5aa52a4aa69d48a0 100644 --- a/crates/gpui/src/app/menu.rs +++ b/crates/gpui/src/app/menu.rs @@ -11,9 +11,46 @@ pub enum MenuItem<'a> { Action { name: &'a str, action: Box, + os_action: Option, }, } +impl<'a> MenuItem<'a> { + pub fn separator() -> Self { + Self::Separator + } + + pub fn submenu(menu: Menu<'a>) -> Self { + Self::Submenu(menu) + } + + pub fn action(name: &'a str, action: impl Action) -> Self { + Self::Action { + name, + action: Box::new(action), + os_action: None, + } + } + + pub fn os_action(name: &'a str, action: impl Action, os_action: OsAction) -> Self { + Self::Action { + name, + action: Box::new(action), + os_action: Some(os_action), + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq)] +pub enum OsAction { + Cut, + Copy, + Paste, + SelectAll, + Undo, + Redo, +} + impl MutableAppContext { pub fn set_menus(&mut self, menus: Vec) { self.foreground_platform diff --git a/crates/gpui/src/elements.rs b/crates/gpui/src/elements.rs index 41a802feb37fcda12c8b68b76759f173b350a793..b77d46536de2da0c59c901d913146704a764bcba 100644 --- a/crates/gpui/src/elements.rs +++ b/crates/gpui/src/elements.rs @@ -363,6 +363,7 @@ impl AnyElement for Lifecycle { value } } + _ => panic!("invalid element lifecycle state"), } } diff --git a/crates/gpui/src/elements/flex.rs b/crates/gpui/src/elements/flex.rs index f6a1a5d8e6e52ec572835d34fe7cc9e0655f664a..ce595222f3f8a3c2e95019f19f790114f05653fd 100644 --- a/crates/gpui/src/elements/flex.rs +++ b/crates/gpui/src/elements/flex.rs @@ -308,7 +308,9 @@ impl Element for Flex { } } } + child.paint(child_origin, visible_bounds, cx); + match self.axis { Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0), Axis::Vertical => child_origin += vec2f(0.0, child.size().y()), diff --git a/crates/gpui/src/keymap_matcher.rs b/crates/gpui/src/keymap_matcher.rs index cfc26d6869e3babec69e891bb86e03b86212d799..0c088f9728977379ee0f8fcd80d5d9d1209f5d20 100644 --- a/crates/gpui/src/keymap_matcher.rs +++ b/crates/gpui/src/keymap_matcher.rs @@ -5,7 +5,7 @@ mod keystroke; use std::{any::TypeId, fmt::Debug}; -use collections::{BTreeMap, HashMap}; +use collections::HashMap; use smallvec::SmallVec; use crate::Action; @@ -68,8 +68,8 @@ impl KeymapMatcher { /// There exist bindings which are still waiting for more keys. /// MatchResult::Complete(matches) => /// 1 or more bindings have recieved the necessary key presses. - /// The order of the matched actions is by order in the keymap file first and - /// position of the matching view second. + /// The order of the matched actions is by position of the matching first, + // and order in the keymap second. pub fn push_keystroke( &mut self, keystroke: Keystroke, @@ -80,8 +80,7 @@ impl KeymapMatcher { // and then the order the binding matched in the view tree second. // The key is the reverse position of the binding in the bindings list so that later bindings // match before earlier ones in the user's config - let mut matched_bindings: BTreeMap)>> = - Default::default(); + let mut matched_bindings: Vec<(usize, Box)> = Default::default(); let first_keystroke = self.pending_keystrokes.is_empty(); self.pending_keystrokes.push(keystroke.clone()); @@ -105,14 +104,11 @@ impl KeymapMatcher { } } - for (order, binding) in self.keymap.bindings().iter().rev().enumerate() { + for binding in self.keymap.bindings().iter().rev() { match binding.match_keys_and_context(&self.pending_keystrokes, &self.contexts[i..]) { BindingMatchResult::Complete(action) => { - matched_bindings - .entry(order) - .or_default() - .push((*view_id, action)); + matched_bindings.push((*view_id, action)); } BindingMatchResult::Partial => { self.pending_views @@ -131,7 +127,7 @@ impl KeymapMatcher { if !matched_bindings.is_empty() { // Collect the sorted matched bindings into the final vec for ease of use // Matched bindings are in order by precedence - MatchResult::Matches(matched_bindings.into_values().flatten().collect()) + MatchResult::Matches(matched_bindings) } else if any_pending { MatchResult::Pending } else { @@ -225,15 +221,47 @@ mod tests { use super::*; + #[test] + fn test_keymap_and_view_ordering() -> Result<()> { + actions!(test, [EditorAction, ProjectPanelAction]); + + let mut editor = KeymapContext::default(); + editor.add_identifier("Editor"); + + let mut project_panel = KeymapContext::default(); + project_panel.add_identifier("ProjectPanel"); + + // Editor 'deeper' in than project panel + let dispatch_path = vec![(2, editor), (1, project_panel)]; + + // But editor actions 'higher' up in keymap + let keymap = Keymap::new(vec![ + Binding::new("left", EditorAction, Some("Editor")), + Binding::new("left", ProjectPanelAction, Some("ProjectPanel")), + ]); + + let mut matcher = KeymapMatcher::new(keymap); + + assert_eq!( + matcher.push_keystroke(Keystroke::parse("left")?, dispatch_path.clone()), + MatchResult::Matches(vec![ + (2, Box::new(EditorAction)), + (1, Box::new(ProjectPanelAction)), + ]), + ); + + Ok(()) + } + #[test] fn test_push_keystroke() -> Result<()> { - actions!(test, [B, AB, C, D, DA]); + actions!(test, [B, AB, C, D, DA, E, EF]); let mut context1 = KeymapContext::default(); - context1.set.insert("1".into()); + context1.add_identifier("1"); let mut context2 = KeymapContext::default(); - context2.set.insert("2".into()); + context2.add_identifier("2"); let dispatch_path = vec![(2, context2), (1, context1)]; @@ -286,6 +314,7 @@ mod tests { matcher.push_keystroke(Keystroke::parse("d")?, dispatch_path.clone()), MatchResult::Matches(vec![(2, Box::new(D)), (1, Box::new(D))]), ); + // If none of the d action handlers consume the binding, a pending // binding may then be used assert_eq!( @@ -366,22 +395,22 @@ mod tests { let predicate = KeymapContextPredicate::parse("a && b || c == d").unwrap(); let mut context = KeymapContext::default(); - context.set.insert("a".into()); + context.add_identifier("a"); assert!(!predicate.eval(&[context])); let mut context = KeymapContext::default(); - context.set.insert("a".into()); - context.set.insert("b".into()); + context.add_identifier("a"); + context.add_identifier("b"); assert!(predicate.eval(&[context])); let mut context = KeymapContext::default(); - context.set.insert("a".into()); - context.map.insert("c".into(), "x".into()); + context.add_identifier("a"); + context.add_key("c", "x"); assert!(!predicate.eval(&[context])); let mut context = KeymapContext::default(); - context.set.insert("a".into()); - context.map.insert("c".into(), "d".into()); + context.add_identifier("a"); + context.add_key("c", "d"); assert!(predicate.eval(&[context])); let predicate = KeymapContextPredicate::parse("!a").unwrap(); @@ -421,10 +450,11 @@ mod tests { assert!(!predicate.eval(&contexts[6..])); fn context_set(names: &[&str]) -> KeymapContext { - KeymapContext { - set: names.iter().copied().map(str::to_string).collect(), - ..Default::default() - } + let mut keymap = KeymapContext::new(); + names + .iter() + .for_each(|name| keymap.add_identifier(name.to_string())); + keymap } } @@ -447,10 +477,10 @@ mod tests { ]); let mut context_a = KeymapContext::default(); - context_a.set.insert("a".into()); + context_a.add_identifier("a"); let mut context_b = KeymapContext::default(); - context_b.set.insert("b".into()); + context_b.add_identifier("b"); let mut matcher = KeymapMatcher::new(keymap); @@ -495,7 +525,7 @@ mod tests { matcher.clear_pending(); let mut context_c = KeymapContext::default(); - context_c.set.insert("c".into()); + context_c.add_identifier("c"); // Pending keystrokes are maintained per-view assert_eq!( diff --git a/crates/gpui/src/keymap_matcher/keymap_context.rs b/crates/gpui/src/keymap_matcher/keymap_context.rs index b19989b2105b3f206f5f3658c2fb72e036a5c35c..bbf6bfc14bad820cd95c1525c5b673f8b8c04dcc 100644 --- a/crates/gpui/src/keymap_matcher/keymap_context.rs +++ b/crates/gpui/src/keymap_matcher/keymap_context.rs @@ -1,13 +1,22 @@ +use std::borrow::Cow; + use anyhow::{anyhow, Result}; use collections::{HashMap, HashSet}; #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct KeymapContext { - pub set: HashSet, - pub map: HashMap, + set: HashSet>, + map: HashMap, Cow<'static, str>>, } impl KeymapContext { + pub fn new() -> Self { + KeymapContext { + set: HashSet::default(), + map: HashMap::default(), + } + } + pub fn extend(&mut self, other: &Self) { for v in &other.set { self.set.insert(v.clone()); @@ -16,6 +25,18 @@ impl KeymapContext { self.map.insert(k.clone(), v.clone()); } } + + pub fn add_identifier>>(&mut self, identifier: I) { + self.set.insert(identifier.into()); + } + + pub fn add_key>, S2: Into>>( + &mut self, + key: S1, + value: S2, + ) { + self.map.insert(key.into(), value.into()); + } } #[derive(Debug, Eq, PartialEq)] @@ -46,12 +67,12 @@ impl KeymapContextPredicate { Self::Identifier(name) => (&context.set).contains(name.as_str()), Self::Equal(left, right) => context .map - .get(left) + .get(left.as_str()) .map(|value| value == right) .unwrap_or(false), Self::NotEqual(left, right) => context .map - .get(left) + .get(left.as_str()) .map(|value| value != right) .unwrap_or(true), Self::Not(pred) => !pred.eval(contexts), diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index 7e3f92a04c6f5bcf349cf87f5b244e00c1231829..aeec02c8caa230c9be46025f0d160ef371f67f06 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -98,6 +98,31 @@ unsafe fn build_classes() { sel!(handleGPUIMenuItem:), handle_menu_item as extern "C" fn(&mut Object, Sel, id), ); + // Add menu item handlers so that OS save panels have the correct key commands + decl.add_method( + sel!(cut:), + handle_menu_item as extern "C" fn(&mut Object, Sel, id), + ); + decl.add_method( + sel!(copy:), + handle_menu_item as extern "C" fn(&mut Object, Sel, id), + ); + decl.add_method( + sel!(paste:), + handle_menu_item as extern "C" fn(&mut Object, Sel, id), + ); + decl.add_method( + sel!(selectAll:), + handle_menu_item as extern "C" fn(&mut Object, Sel, id), + ); + decl.add_method( + sel!(undo:), + handle_menu_item as extern "C" fn(&mut Object, Sel, id), + ); + decl.add_method( + sel!(redo:), + handle_menu_item as extern "C" fn(&mut Object, Sel, id), + ); decl.add_method( sel!(validateMenuItem:), validate_menu_item as extern "C" fn(&mut Object, Sel, id) -> bool, @@ -193,11 +218,25 @@ impl MacForegroundPlatform { ) -> id { match item { MenuItem::Separator => NSMenuItem::separatorItem(nil), - MenuItem::Action { name, action } => { + MenuItem::Action { + name, + action, + os_action, + } => { + // TODO let keystrokes = keystroke_matcher .bindings_for_action_type(action.as_any().type_id()) .find(|binding| binding.action().eq(action.as_ref())) .map(|binding| binding.keystrokes()); + let selector = match os_action { + Some(crate::OsAction::Cut) => selector("cut:"), + Some(crate::OsAction::Copy) => selector("copy:"), + Some(crate::OsAction::Paste) => selector("paste:"), + Some(crate::OsAction::SelectAll) => selector("selectAll:"), + Some(crate::OsAction::Undo) => selector("undo:"), + Some(crate::OsAction::Redo) => selector("redo:"), + None => selector("handleGPUIMenuItem:"), + }; let item; if let Some(keystrokes) = keystrokes { @@ -218,7 +257,7 @@ impl MacForegroundPlatform { item = NSMenuItem::alloc(nil) .initWithTitle_action_keyEquivalent_( ns_string(name), - selector("handleGPUIMenuItem:"), + selector, ns_string(key_to_native(&keystroke.key).as_ref()), ) .autorelease(); @@ -240,7 +279,7 @@ impl MacForegroundPlatform { item = NSMenuItem::alloc(nil) .initWithTitle_action_keyEquivalent_( ns_string(&name), - selector("handleGPUIMenuItem:"), + selector, ns_string(""), ) .autorelease(); @@ -249,7 +288,7 @@ impl MacForegroundPlatform { item = NSMenuItem::alloc(nil) .initWithTitle_action_keyEquivalent_( ns_string(name), - selector("handleGPUIMenuItem:"), + selector, ns_string(""), ) .autorelease(); diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 50e0de216c5a8e8e7476ac6832d9e27b6afd8a9e..19173707fb13f1fa732b3bc20659dd6f0e6a4991 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -737,6 +737,7 @@ impl platform::Window for Window { let title = ns_string(title); let _: () = msg_send![app, changeWindowsItem:window title:title filename:false]; let _: () = msg_send![window, setTitle: title]; + self.0.borrow().move_traffic_light(); } } diff --git a/crates/live_kit_client/examples/test_app.rs b/crates/live_kit_client/examples/test_app.rs index a92b106d3365a7717b90ca666b1abca3acbff1ac..14332b3e06c922ca3ca965bb7ffe74fa1535cb8b 100644 --- a/crates/live_kit_client/examples/test_app.rs +++ b/crates/live_kit_client/examples/test_app.rs @@ -20,6 +20,7 @@ fn main() { items: vec![MenuItem::Action { name: "Quit", action: Box::new(Quit), + os_action: None, }], }]); diff --git a/crates/picker/src/picker.rs b/crates/picker/src/picker.rs index cf3ca6e994e97adc7facd98efa7a0f01366ee485..e4d062d575aa6a44909d0a757c766470987abb87 100644 --- a/crates/picker/src/picker.rs +++ b/crates/picker/src/picker.rs @@ -126,7 +126,7 @@ impl View for Picker { fn keymap_context(&self, _: &AppContext) -> KeymapContext { let mut cx = Self::default_keymap_context(); - cx.set.insert("menu".into()); + cx.add_identifier("menu"); cx } diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 2ba920c318f04a24cc0f84321ebd7fe0d4b90c82..4b3c5b7bc53a452b8b6d11ec41e7e41b8228a7f3 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1314,7 +1314,7 @@ impl View for ProjectPanel { fn keymap_context(&self, _: &AppContext) -> KeymapContext { let mut cx = Self::default_keymap_context(); - cx.set.insert("menu".into()); + cx.add_identifier("menu"); cx } } diff --git a/crates/rope/Cargo.toml b/crates/rope/Cargo.toml index 8257ef4a6e7793bb9818dcda92418af4e6baeb02..4566b8397d8f6062d89eb2cbaa37e94872987e81 100644 --- a/crates/rope/Cargo.toml +++ b/crates/rope/Cargo.toml @@ -8,7 +8,7 @@ publish = false path = "src/rope.rs" [dependencies] -bromberg_sl2 = { git = "https://github.com/zed-industries/bromberg_sl2", rev = "dac565a90e8f9245f48ff46225c915dc50f76920" } +bromberg_sl2 = { git = "https://github.com/zed-industries/bromberg_sl2", rev = "950bc5482c216c395049ae33ae4501e08975f17f" } smallvec = { version = "1.6", features = ["union"] } sum_tree = { path = "../sum_tree" } arrayvec = "0.7.1" diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index ba481ce45ba7ab148d2f9b347d5d483e448b44f6..ff7a882f1a86dd64f915c7ccf251bc70d1038546 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -16,7 +16,7 @@ message Envelope { Error error = 6; Ping ping = 7; Test test = 8; - + CreateRoom create_room = 9; CreateRoomResponse create_room_response = 10; JoinRoom join_room = 11; @@ -206,7 +206,8 @@ message Room { uint64 id = 1; repeated Participant participants = 2; repeated PendingParticipant pending_participants = 3; - string live_kit_room = 4; + repeated Follower followers = 4; + string live_kit_room = 5; } message Participant { @@ -227,6 +228,12 @@ message ParticipantProject { repeated string worktree_root_names = 2; } +message Follower { + PeerId leader_id = 1; + PeerId follower_id = 2; + uint64 project_id = 3; +} + message ParticipantLocation { oneof variant { SharedProject shared_project = 1; diff --git a/crates/rpc/src/rpc.rs b/crates/rpc/src/rpc.rs index b05bc17906063fe51dc4ec349496f26a1338a9dc..4f5f126516a4b4e11171847fc0107940b344e669 100644 --- a/crates/rpc/src/rpc.rs +++ b/crates/rpc/src/rpc.rs @@ -6,4 +6,4 @@ pub use conn::Connection; pub use peer::*; mod macros; -pub const PROTOCOL_VERSION: u32 = 46; +pub const PROTOCOL_VERSION: u32 = 49; diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index b2e3c81bc98eeec0531b1f6cebbdd1fbd5d3398e..04f4b1470a72b1675def55f1af08df2fb220b760 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -248,15 +248,15 @@ impl Item for ProjectSearchView { tab_theme: &theme::Tab, cx: &gpui::AppContext, ) -> ElementBox { - let settings = cx.global::(); - let search_theme = &settings.theme.search; Flex::row() .with_child( Svg::new("icons/magnifying_glass_12.svg") .with_color(tab_theme.label.text.color) .constrained() - .with_width(search_theme.tab_icon_width) + .with_width(tab_theme.icon_width) .aligned() + .contained() + .with_margin_right(tab_theme.spacing) .boxed(), ) .with_children(self.model.read(cx).active_query.as_ref().map(|query| { @@ -264,8 +264,6 @@ impl Item for ProjectSearchView { Label::new(query_text, tab_theme.label.clone()) .aligned() - .contained() - .with_margin_left(search_theme.tab_icon_spacing) .boxed() })) .boxed() diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 58caf7d45d7518d093181d9d892cbe9eabc144fc..7400950c3b8c1f59287df406a8fb6f86df44681c 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -21,7 +21,7 @@ use gpui::{ use project::{LocalWorktree, Project}; use serde::Deserialize; use settings::{Settings, TerminalBlink, WorkingDirectory}; -use smallvec::SmallVec; +use smallvec::{smallvec, SmallVec}; use smol::Timer; use terminal::{ alacritty_terminal::{ @@ -469,53 +469,50 @@ impl View for TerminalView { let mut context = Self::default_keymap_context(); let mode = self.terminal.read(cx).last_content.mode; - context.map.insert( - "screen".to_string(), - (if mode.contains(TermMode::ALT_SCREEN) { + context.add_key( + "screen", + if mode.contains(TermMode::ALT_SCREEN) { "alt" } else { "normal" - }) - .to_string(), + }, ); if mode.contains(TermMode::APP_CURSOR) { - context.set.insert("DECCKM".to_string()); + context.add_identifier("DECCKM"); } if mode.contains(TermMode::APP_KEYPAD) { - context.set.insert("DECPAM".to_string()); - } - //Note the ! here - if !mode.contains(TermMode::APP_KEYPAD) { - context.set.insert("DECPNM".to_string()); + context.add_identifier("DECPAM"); + } else { + context.add_identifier("DECPNM"); } if mode.contains(TermMode::SHOW_CURSOR) { - context.set.insert("DECTCEM".to_string()); + context.add_identifier("DECTCEM"); } if mode.contains(TermMode::LINE_WRAP) { - context.set.insert("DECAWM".to_string()); + context.add_identifier("DECAWM"); } if mode.contains(TermMode::ORIGIN) { - context.set.insert("DECOM".to_string()); + context.add_identifier("DECOM"); } if mode.contains(TermMode::INSERT) { - context.set.insert("IRM".to_string()); + context.add_identifier("IRM"); } //LNM is apparently the name for this. https://vt100.net/docs/vt510-rm/LNM.html if mode.contains(TermMode::LINE_FEED_NEW_LINE) { - context.set.insert("LNM".to_string()); + context.add_identifier("LNM"); } if mode.contains(TermMode::FOCUS_IN_OUT) { - context.set.insert("report_focus".to_string()); + context.add_identifier("report_focus"); } if mode.contains(TermMode::ALTERNATE_SCROLL) { - context.set.insert("alternate_scroll".to_string()); + context.add_identifier("alternate_scroll"); } if mode.contains(TermMode::BRACKETED_PASTE) { - context.set.insert("bracketed_paste".to_string()); + context.add_identifier("bracketed_paste"); } if mode.intersects(TermMode::MOUSE_MODE) { - context.set.insert("any_mouse_reporting".to_string()); + context.add_identifier("any_mouse_reporting"); } { let mouse_reporting = if mode.contains(TermMode::MOUSE_REPORT_CLICK) { @@ -527,9 +524,7 @@ impl View for TerminalView { } else { "off" }; - context - .map - .insert("mouse_reporting".to_string(), mouse_reporting.to_string()); + context.add_key("mouse_reporting", mouse_reporting); } { let format = if mode.contains(TermMode::SGR_MOUSE) { @@ -539,9 +534,7 @@ impl View for TerminalView { } else { "normal" }; - context - .map - .insert("mouse_format".to_string(), format.to_string()); + context.add_key("mouse_format", format); } context } @@ -589,11 +582,16 @@ impl Item for TerminalView { Flex::row() .with_child( - Label::new(title, tab_theme.label.clone()) + gpui::elements::Svg::new("icons/terminal_12.svg") + .with_color(tab_theme.label.text.color) + .constrained() + .with_width(tab_theme.icon_width) .aligned() .contained() + .with_margin_right(tab_theme.spacing) .boxed(), ) + .with_child(Label::new(title, tab_theme.label.clone()).aligned().boxed()) .boxed() } @@ -616,43 +614,6 @@ impl Item for TerminalView { None } - fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project::Item)) {} - - fn is_singleton(&self, _cx: &gpui::AppContext) -> bool { - false - } - - fn set_nav_history(&mut self, _: workspace::ItemNavHistory, _: &mut ViewContext) {} - - fn can_save(&self, _cx: &gpui::AppContext) -> bool { - false - } - - fn save( - &mut self, - _project: gpui::ModelHandle, - _cx: &mut ViewContext, - ) -> gpui::Task> { - unreachable!("save should not have been called"); - } - - fn save_as( - &mut self, - _project: gpui::ModelHandle, - _abs_path: std::path::PathBuf, - _cx: &mut ViewContext, - ) -> gpui::Task> { - unreachable!("save_as should not have been called"); - } - - fn reload( - &mut self, - _project: gpui::ModelHandle, - _cx: &mut ViewContext, - ) -> gpui::Task> { - gpui::Task::ready(Ok(())) - } - fn is_dirty(&self, _cx: &gpui::AppContext) -> bool { self.has_bell() } @@ -667,10 +628,10 @@ impl Item for TerminalView { fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> { match event { - Event::BreadcrumbsChanged => smallvec::smallvec![ItemEvent::UpdateBreadcrumbs], - Event::TitleChanged | Event::Wakeup => smallvec::smallvec![ItemEvent::UpdateTab], - Event::CloseTerminal => smallvec::smallvec![ItemEvent::CloseItem], - _ => smallvec::smallvec![], + Event::BreadcrumbsChanged => smallvec![ItemEvent::UpdateBreadcrumbs], + Event::TitleChanged | Event::Wakeup => smallvec![ItemEvent::UpdateTab], + Event::CloseTerminal => smallvec![ItemEvent::CloseItem], + _ => smallvec![], } } diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index ba66ea2d86c79791d7aeceac5f8d4c8655e79f5b..efe64cbc5c6e13de876c757847f3f96514ff3a85 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -74,20 +74,32 @@ pub struct Titlebar { pub container: ContainerStyle, pub height: f32, pub title: TextStyle, - pub avatar_width: f32, - pub avatar_margin: f32, + pub item_spacing: f32, + pub face_pile_spacing: f32, pub avatar_ribbon: AvatarRibbon, + pub follower_avatar_overlap: f32, + pub leader_selection: ContainerStyle, pub offline_icon: OfflineIcon, - pub avatar: ImageStyle, - pub inactive_avatar: ImageStyle, + pub leader_avatar: AvatarStyle, + pub follower_avatar: AvatarStyle, + pub inactive_avatar_grayscale: bool, pub sign_in_prompt: Interactive, pub outdated_warning: ContainedText, pub share_button: Interactive, pub call_control: Interactive, pub toggle_contacts_button: Interactive, + pub user_menu_button: Interactive, pub toggle_contacts_badge: ContainerStyle, } +#[derive(Copy, Clone, Deserialize, Default)] +pub struct AvatarStyle { + #[serde(flatten)] + pub image: ImageStyle, + pub outer_width: f32, + pub outer_corner_radius: f32, +} + #[derive(Deserialize, Default)] pub struct ContactsPopover { #[serde(flatten)] @@ -246,8 +258,6 @@ pub struct Search { pub match_background: Color, pub match_index: ContainedText, pub results_status: TextStyle, - pub tab_icon_width: f32, - pub tab_icon_spacing: f32, pub dismiss_button: Interactive, } @@ -381,7 +391,7 @@ pub struct InviteLink { pub icon: Icon, } -#[derive(Deserialize, Default)] +#[derive(Deserialize, Clone, Copy, Default)] pub struct Icon { #[serde(flatten)] pub container: ContainerStyle, diff --git a/crates/theme_testbench/src/theme_testbench.rs b/crates/theme_testbench/src/theme_testbench.rs index 573e95e198df1f8444d3290856b49a03626b869e..ad01f51a1535f8d49928f272f42c49624c858ab7 100644 --- a/crates/theme_testbench/src/theme_testbench.rs +++ b/crates/theme_testbench/src/theme_testbench.rs @@ -11,12 +11,8 @@ use gpui::{ }; use project::Project; use settings::Settings; -use smallvec::SmallVec; use theme::{ColorScheme, Layer, Style, StyleSet}; -use workspace::{ - item::{Item, ItemEvent}, - register_deserializable_item, Pane, Workspace, -}; +use workspace::{item::Item, register_deserializable_item, Pane, Workspace}; actions!(theme, [DeployThemeTestbench]); @@ -314,47 +310,6 @@ impl Item for ThemeTestbench { .boxed() } - fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project::Item)) {} - - fn is_singleton(&self, _: &AppContext) -> bool { - false - } - - fn set_nav_history(&mut self, _: workspace::ItemNavHistory, _: &mut ViewContext) {} - - fn can_save(&self, _: &AppContext) -> bool { - false - } - - fn save( - &mut self, - _: gpui::ModelHandle, - _: &mut ViewContext, - ) -> gpui::Task> { - unreachable!("save should not have been called"); - } - - fn save_as( - &mut self, - _: gpui::ModelHandle, - _: std::path::PathBuf, - _: &mut ViewContext, - ) -> gpui::Task> { - unreachable!("save_as should not have been called"); - } - - fn reload( - &mut self, - _: gpui::ModelHandle, - _: &mut ViewContext, - ) -> gpui::Task> { - gpui::Task::ready(Ok(())) - } - - fn to_item_events(_: &Self::Event) -> SmallVec<[ItemEvent; 2]> { - SmallVec::new() - } - fn serialized_item_kind() -> Option<&'static str> { Some("ThemeTestBench") } diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index 25188a466cffa309bac6008dd854bdaf149482fb..1794e999a265a91c7e0b3ec3691c094dcdab8600 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -36,6 +36,7 @@ pub enum Motion { Matching, FindForward { before: bool, text: Arc }, FindBackward { after: bool, text: Arc }, + NextLineStart, } #[derive(Clone, Deserialize, PartialEq)] @@ -74,6 +75,7 @@ actions!( StartOfDocument, EndOfDocument, Matching, + NextLineStart, ] ); impl_actions!(vim, [NextWordStart, NextWordEnd, PreviousWordStart]); @@ -111,6 +113,7 @@ pub fn init(cx: &mut MutableAppContext) { &PreviousWordStart { ignore_punctuation }: &PreviousWordStart, cx: _| { motion(Motion::PreviousWordStart { ignore_punctuation }, cx) }, ); + cx.add_action(|_: &mut Workspace, &NextLineStart, cx: _| motion(Motion::NextLineStart, cx)) } pub(crate) fn motion(motion: Motion, cx: &mut MutableAppContext) { @@ -138,15 +141,43 @@ pub(crate) fn motion(motion: Motion, cx: &mut MutableAppContext) { impl Motion { pub fn linewise(&self) -> bool { use Motion::*; - matches!( - self, - Down | Up | StartOfDocument | EndOfDocument | CurrentLine - ) + match self { + Down | Up | StartOfDocument | EndOfDocument | CurrentLine | NextLineStart => true, + EndOfLine + | NextWordEnd { .. } + | Matching + | FindForward { .. } + | Left + | Backspace + | Right + | StartOfLine + | NextWordStart { .. } + | PreviousWordStart { .. } + | FirstNonWhitespace + | FindBackward { .. } => false, + } } pub fn infallible(&self) -> bool { use Motion::*; - matches!(self, StartOfDocument | CurrentLine | EndOfDocument) + match self { + StartOfDocument | EndOfDocument | CurrentLine => true, + Down + | Up + | EndOfLine + | NextWordEnd { .. } + | Matching + | FindForward { .. } + | Left + | Backspace + | Right + | StartOfLine + | NextWordStart { .. } + | PreviousWordStart { .. } + | FirstNonWhitespace + | FindBackward { .. } + | NextLineStart => false, + } } pub fn inclusive(&self) -> bool { @@ -160,7 +191,8 @@ impl Motion { | EndOfLine | NextWordEnd { .. } | Matching - | FindForward { .. } => true, + | FindForward { .. } + | NextLineStart => true, Left | Backspace | Right @@ -214,6 +246,7 @@ impl Motion { find_backward(map, point, *after, text.clone(), times), SelectionGoal::None, ), + NextLineStart => (next_line_start(map, point, times), SelectionGoal::None), }; (new_point != point || infallible).then_some((new_point, goal)) @@ -543,3 +576,8 @@ fn find_backward( }) .unwrap_or(from) } + +fn next_line_start(map: &DisplaySnapshot, point: DisplayPoint, times: usize) -> DisplayPoint { + let new_row = (point.row() + times as u32).min(map.max_buffer_row()); + map.clip_point(DisplayPoint::new(new_row, 0), Bias::Left) +} diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index 0cac45fd1859e3950ef38e733525702295cfd8c5..55134e21494bea5e840fa4f6067f26c8bd8b72d7 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -473,6 +473,7 @@ pub(crate) fn normal_replace(text: Arc, cx: &mut MutableAppContext) { #[cfg(test)] mod test { + use gpui::TestAppContext; use indoc::indoc; use crate::{ @@ -515,15 +516,15 @@ mod test { .await; } - // #[gpui::test] - // async fn test_enter(cx: &mut gpui::TestAppContext) { - // let mut cx = NeovimBackedTestContext::new(cx).await.binding(["enter"]); - // cx.assert_all(indoc! {" - // ˇThe qˇuick broˇwn - // ˇfox jumps" - // }) - // .await; - // } + #[gpui::test] + async fn test_enter(cx: &mut gpui::TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await.binding(["enter"]); + cx.assert_all(indoc! {" + ˇThe qˇuick broˇwn + ˇfox jumps" + }) + .await; + } #[gpui::test] async fn test_k(cx: &mut gpui::TestAppContext) { @@ -1030,7 +1031,7 @@ mod test { } #[gpui::test] - async fn test_percent(cx: &mut gpui::TestAppContext) { + async fn test_percent(cx: &mut TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await.binding(["%"]); cx.assert_all("ˇconsole.logˇ(ˇvaˇrˇ)ˇ;").await; cx.assert_all("ˇconsole.logˇ(ˇ'var', ˇ[ˇ1, ˇ2, 3ˇ]ˇ)ˇ;") diff --git a/crates/vim/src/state.rs b/crates/vim/src/state.rs index 5734a9222da94927f4ede311f493d8e84744af16..e1a06fce59d6ff5f9529552e422fbd0643418de1 100644 --- a/crates/vim/src/state.rs +++ b/crates/vim/src/state.rs @@ -73,34 +73,30 @@ impl VimState { pub fn keymap_context_layer(&self) -> KeymapContext { let mut context = KeymapContext::default(); - context.map.insert( - "vim_mode".to_string(), + context.add_key( + "vim_mode", match self.mode { Mode::Normal => "normal", Mode::Visual { .. } => "visual", Mode::Insert => "insert", - } - .to_string(), + }, ); if self.vim_controlled() { - context.set.insert("VimControl".to_string()); + context.add_identifier("VimControl"); } let active_operator = self.operator_stack.last(); if let Some(active_operator) = active_operator { for context_flag in active_operator.context_flags().into_iter() { - context.set.insert(context_flag.to_string()); + context.add_identifier(*context_flag); } } - context.map.insert( - "vim_operator".to_string(), - active_operator - .map(|op| op.id()) - .unwrap_or_else(|| "none") - .to_string(), + context.add_key( + "vim_operator", + active_operator.map(|op| op.id()).unwrap_or_else(|| "none"), ); context diff --git a/crates/vim/test_data/test_enter.json b/crates/vim/test_data/test_enter.json new file mode 100644 index 0000000000000000000000000000000000000000..1bbc077812c5216dbf4046082ae6f737d382e8d6 --- /dev/null +++ b/crates/vim/test_data/test_enter.json @@ -0,0 +1 @@ +[{"Text":"The quick brown\nfox jumps"},{"Mode":"Normal"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Normal"},{"Text":"The quick brown\nfox jumps"},{"Mode":"Normal"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Normal"},{"Text":"The quick brown\nfox jumps"},{"Mode":"Normal"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Normal"},{"Text":"The quick brown\nfox jumps"},{"Mode":"Normal"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Normal"}] \ No newline at end of file diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 0e28976151d8ae64119071851528b9cfdbfb6b2b..a80b9f8d83d9dc4d407b337a944537143e24581e 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -49,9 +49,11 @@ pub trait Item: View { } fn tab_content(&self, detail: Option, style: &theme::Tab, cx: &AppContext) -> ElementBox; - fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project::Item)); - fn is_singleton(&self, cx: &AppContext) -> bool; - fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext); + fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project::Item)) {} + fn is_singleton(&self, _cx: &AppContext) -> bool { + false + } + fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext) {} fn clone_on_split(&self, _workspace_id: WorkspaceId, _: &mut ViewContext) -> Option where Self: Sized, @@ -64,23 +66,31 @@ pub trait Item: View { fn has_conflict(&self, _: &AppContext) -> bool { false } - fn can_save(&self, cx: &AppContext) -> bool; + fn can_save(&self, _cx: &AppContext) -> bool { + false + } fn save( &mut self, - project: ModelHandle, - cx: &mut ViewContext, - ) -> Task>; + _project: ModelHandle, + _cx: &mut ViewContext, + ) -> Task> { + unimplemented!("save() must be implemented if can_save() returns true") + } fn save_as( &mut self, - project: ModelHandle, - abs_path: PathBuf, - cx: &mut ViewContext, - ) -> Task>; + _project: ModelHandle, + _abs_path: PathBuf, + _cx: &mut ViewContext, + ) -> Task> { + unimplemented!("save_as() must be implemented if can_save() returns true") + } fn reload( &mut self, - project: ModelHandle, - cx: &mut ViewContext, - ) -> Task>; + _project: ModelHandle, + _cx: &mut ViewContext, + ) -> Task> { + unimplemented!("reload() must be implemented if can_save() returns true") + } fn git_diff_recalc( &mut self, _project: ModelHandle, @@ -88,7 +98,9 @@ pub trait Item: View { ) -> Task> { Task::ready(Ok(())) } - fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]>; + fn to_item_events(_event: &Self::Event) -> SmallVec<[ItemEvent; 2]> { + SmallVec::new() + } fn should_close_item_on_event(_: &Self::Event) -> bool { false } @@ -124,15 +136,21 @@ pub trait Item: View { fn added_to_workspace(&mut self, _workspace: &mut Workspace, _cx: &mut ViewContext) {} - fn serialized_item_kind() -> Option<&'static str>; + fn serialized_item_kind() -> Option<&'static str> { + None + } fn deserialize( - project: ModelHandle, - workspace: WeakViewHandle, - workspace_id: WorkspaceId, - item_id: ItemId, - cx: &mut ViewContext, - ) -> Task>>; + _project: ModelHandle, + _workspace: WeakViewHandle, + _workspace_id: WorkspaceId, + _item_id: ItemId, + _cx: &mut ViewContext, + ) -> Task>> { + unimplemented!( + "deserialize() must be implemented if serialized_item_kind() returns Some(_)" + ) + } } pub trait ItemHandle: 'static + fmt::Debug { diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 8e51a54178cceca5cbc9dbae70ba06be8007e3a1..2e5565ea63f5dd5d7dea68aed931cc6322762402 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -21,6 +21,7 @@ use gpui::{ vector::{vec2f, Vector2F}, }, impl_actions, impl_internal_actions, + keymap_matcher::KeymapContext, platform::{CursorStyle, NavigationDirection}, Action, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, EventContext, ModelHandle, MouseButton, MutableAppContext, PromptLevel, Quad, RenderContext, Task, View, @@ -1550,6 +1551,14 @@ impl View for Pane { } } } + + fn keymap_context(&self, _: &AppContext) -> KeymapContext { + let mut keymap = Self::default_keymap_context(); + if self.docked.is_some() { + keymap.add_identifier("docked"); + } + keymap + } } fn tab_bar_button( diff --git a/crates/workspace/src/shared_screen.rs b/crates/workspace/src/shared_screen.rs index b3e107c81b0030b763326ac16c806c1c8c2aa0af..43e7f24f627f23ae9b7507077f512a65c93068a2 100644 --- a/crates/workspace/src/shared_screen.rs +++ b/crates/workspace/src/shared_screen.rs @@ -1,23 +1,18 @@ use crate::{ - item::ItemEvent, persistence::model::ItemId, Item, ItemNavHistory, Pane, Workspace, WorkspaceId, + item::{Item, ItemEvent}, + ItemNavHistory, WorkspaceId, }; -use anyhow::{anyhow, Result}; use call::participant::{Frame, RemoteVideoTrack}; use client::{proto::PeerId, User}; use futures::StreamExt; use gpui::{ elements::*, geometry::{rect::RectF, vector::vec2f}, - AppContext, Entity, ModelHandle, MouseButton, RenderContext, Task, View, ViewContext, - ViewHandle, WeakViewHandle, + AppContext, Entity, MouseButton, RenderContext, Task, View, ViewContext, }; -use project::Project; use settings::Settings; use smallvec::SmallVec; -use std::{ - path::PathBuf, - sync::{Arc, Weak}, -}; +use std::sync::{Arc, Weak}; pub enum Event { Close, @@ -130,12 +125,6 @@ impl Item for SharedScreen { .boxed() } - fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project::Item)) {} - - fn is_singleton(&self, _: &AppContext) -> bool { - false - } - fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext) { self.nav_history = Some(history); } @@ -149,52 +138,9 @@ impl Item for SharedScreen { Some(Self::new(&track, self.peer_id, self.user.clone(), cx)) } - fn can_save(&self, _: &AppContext) -> bool { - false - } - - fn save( - &mut self, - _: ModelHandle, - _: &mut ViewContext, - ) -> Task> { - Task::ready(Err(anyhow!("Item::save called on SharedScreen"))) - } - - fn save_as( - &mut self, - _: ModelHandle, - _: PathBuf, - _: &mut ViewContext, - ) -> Task> { - Task::ready(Err(anyhow!("Item::save_as called on SharedScreen"))) - } - - fn reload( - &mut self, - _: ModelHandle, - _: &mut ViewContext, - ) -> Task> { - Task::ready(Err(anyhow!("Item::reload called on SharedScreen"))) - } - fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> { match event { Event::Close => smallvec::smallvec!(ItemEvent::CloseItem), } } - - fn serialized_item_kind() -> Option<&'static str> { - None - } - - fn deserialize( - _project: ModelHandle, - _workspace: WeakViewHandle, - _workspace_id: WorkspaceId, - _item_id: ItemId, - _cx: &mut ViewContext, - ) -> Task>> { - unreachable!("Shared screen can not be deserialized") - } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index d61fc3774c45b02c0077481a172cc78f150244bf..c134c7f68c9e04a7dd03a8ed86fc5959632745c7 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -837,7 +837,7 @@ impl Workspace { &self.project } - pub fn client(&self) -> &Arc { + pub fn client(&self) -> &Client { &self.client } @@ -1589,13 +1589,17 @@ impl Workspace { } let item = pane.read(cx).active_item()?; - let new_pane = self.add_pane(cx); - if let Some(clone) = item.clone_on_split(self.database_id(), cx.as_mut()) { - Pane::add_item(self, &new_pane, clone, true, true, None, cx); - } - self.center.split(&pane, &new_pane, direction).unwrap(); + let maybe_pane_handle = + if let Some(clone) = item.clone_on_split(self.database_id(), cx.as_mut()) { + let new_pane = self.add_pane(cx); + Pane::add_item(self, &new_pane, clone, true, true, None, cx); + self.center.split(&pane, &new_pane, direction).unwrap(); + Some(new_pane) + } else { + None + }; cx.notify(); - Some(new_pane) + maybe_pane_handle } pub fn split_pane_with_item(&mut self, action: &SplitWithItem, cx: &mut ViewContext) { @@ -1828,24 +1832,15 @@ impl Workspace { None } - pub fn is_following(&self, peer_id: PeerId) -> bool { + pub fn is_being_followed(&self, peer_id: PeerId) -> bool { self.follower_states_by_leader.contains_key(&peer_id) } - pub fn is_followed(&self, peer_id: PeerId) -> bool { + pub fn is_followed_by(&self, peer_id: PeerId) -> bool { self.leader_state.followers.contains(&peer_id) } fn render_titlebar(&self, theme: &Theme, cx: &mut RenderContext) -> ElementBox { - let project = &self.project.read(cx); - let mut worktree_root_names = String::new(); - for (i, name) in project.worktree_root_names(cx).enumerate() { - if i > 0 { - worktree_root_names.push_str(", "); - } - worktree_root_names.push_str(name); - } - // TODO: There should be a better system in place for this // (https://github.com/zed-industries/zed/issues/1290) let is_fullscreen = cx.window_is_fullscreen(cx.window_id()); @@ -1862,16 +1857,10 @@ impl Workspace { MouseEventHandler::::new(0, cx, |_, cx| { Container::new( Stack::new() - .with_child( - Label::new(worktree_root_names, theme.workspace.titlebar.title.clone()) - .aligned() - .left() - .boxed(), - ) .with_children( self.titlebar_item .as_ref() - .map(|item| ChildView::new(item, cx).aligned().right().boxed()), + .map(|item| ChildView::new(item, cx).boxed()), ) .boxed(), ) @@ -2727,11 +2716,7 @@ impl View for Workspace { } fn keymap_context(&self, _: &AppContext) -> KeymapContext { - let mut keymap = Self::default_keymap_context(); - if self.active_pane() == self.dock_pane() { - keymap.set.insert("Dock".into()); - } - keymap + Self::default_keymap_context() } } diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 046866cc0c0954a4f9a7a0668e961fdf4ad6e9bc..19c9a3d727704176f7d36b216719a3584d8c4a8d 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] description = "The fast, collaborative code editor." edition = "2021" name = "zed" -version = "0.75.0" +version = "0.76.0" publish = false [lib] diff --git a/crates/zed/resources/app-icon-preview.png b/crates/zed/resources/app-icon-preview.png index 2d6ade1dd348a934ebec7342c0249560125d0bb7..ce2a639e2cd9c77ee5bd56bb13fe397414110c0c 100644 Binary files a/crates/zed/resources/app-icon-preview.png and b/crates/zed/resources/app-icon-preview.png differ diff --git a/crates/zed/resources/app-icon-preview@2x.png b/crates/zed/resources/app-icon-preview@2x.png index 0a55727e413ad5679cffbd8c14d2f1086f6b572f..f22b8523f9fab4a66c5de3cd75ecbbacfb387766 100644 Binary files a/crates/zed/resources/app-icon-preview@2x.png and b/crates/zed/resources/app-icon-preview@2x.png differ diff --git a/crates/zed/resources/app-icon.png b/crates/zed/resources/app-icon.png index 6c8a1b6bd42daf6654c6c1808e58fba3c8c9e140..08b6d8afa0d1088e50f1e9697e01407c9c623f6d 100644 Binary files a/crates/zed/resources/app-icon.png and b/crates/zed/resources/app-icon.png differ diff --git a/crates/zed/resources/app-icon@2x.png b/crates/zed/resources/app-icon@2x.png index 3509df92caf25f4b25dfdc7e8c93a946f7d94354..5bb5754bc19c1ae81a7f1c1e48e0aa1c739486e3 100644 Binary files a/crates/zed/resources/app-icon@2x.png and b/crates/zed/resources/app-icon@2x.png differ diff --git a/crates/zed/src/menus.rs b/crates/zed/src/menus.rs index 52ca7d232494506d88fa5d978368267df0a3fb91..bb519c7a9505bc2de07fa60c70014be3b4fbc169 100644 --- a/crates/zed/src/menus.rs +++ b/crates/zed/src/menus.rs @@ -1,4 +1,4 @@ -use gpui::{Menu, MenuItem}; +use gpui::{Menu, MenuItem, OsAction}; #[cfg(target_os = "macos")] pub fn menus() -> Vec> { @@ -6,363 +6,159 @@ pub fn menus() -> Vec> { Menu { name: "Zed", items: vec![ - MenuItem::Action { - name: "About Zed…", - action: Box::new(super::About), - }, - MenuItem::Action { - name: "Check for Updates", - action: Box::new(auto_update::Check), - }, - MenuItem::Separator, - MenuItem::Submenu(Menu { + MenuItem::action("About Zed…", super::About), + MenuItem::action("Check for Updates", auto_update::Check), + MenuItem::separator(), + MenuItem::submenu(Menu { name: "Preferences", items: vec![ - MenuItem::Action { - name: "Open Settings", - action: Box::new(super::OpenSettings), - }, - MenuItem::Action { - name: "Open Key Bindings", - action: Box::new(super::OpenKeymap), - }, - MenuItem::Action { - name: "Open Default Settings", - action: Box::new(super::OpenDefaultSettings), - }, - MenuItem::Action { - name: "Open Default Key Bindings", - action: Box::new(super::OpenDefaultKeymap), - }, - MenuItem::Action { - name: "Select Theme", - action: Box::new(theme_selector::Toggle), - }, + MenuItem::action("Open Settings", super::OpenSettings), + MenuItem::action("Open Key Bindings", super::OpenKeymap), + MenuItem::action("Open Default Settings", super::OpenDefaultSettings), + MenuItem::action("Open Default Key Bindings", super::OpenDefaultKeymap), + MenuItem::action("Select Theme", theme_selector::Toggle), ], }), - MenuItem::Action { - name: "Install CLI", - action: Box::new(super::InstallCommandLineInterface), - }, - MenuItem::Separator, - MenuItem::Action { - name: "Hide Zed", - action: Box::new(super::Hide), - }, - MenuItem::Action { - name: "Hide Others", - action: Box::new(super::HideOthers), - }, - MenuItem::Action { - name: "Show All", - action: Box::new(super::ShowAll), - }, - MenuItem::Action { - name: "Quit", - action: Box::new(super::Quit), - }, + MenuItem::action("Install CLI", super::InstallCommandLineInterface), + MenuItem::separator(), + MenuItem::action("Hide Zed", super::Hide), + MenuItem::action("Hide Others", super::HideOthers), + MenuItem::action("Show All", super::ShowAll), + MenuItem::action("Quit", super::Quit), ], }, Menu { name: "File", items: vec![ - MenuItem::Action { - name: "New", - action: Box::new(workspace::NewFile), - }, - MenuItem::Action { - name: "New Window", - action: Box::new(workspace::NewWindow), - }, - MenuItem::Separator, - MenuItem::Action { - name: "Open…", - action: Box::new(workspace::Open), - }, - MenuItem::Action { - name: "Open Recent...", - action: Box::new(recent_projects::OpenRecent), - }, - MenuItem::Separator, - MenuItem::Action { - name: "Add Folder to Project…", - action: Box::new(workspace::AddFolderToProject), - }, - MenuItem::Action { - name: "Save", - action: Box::new(workspace::Save), - }, - MenuItem::Action { - name: "Save As…", - action: Box::new(workspace::SaveAs), - }, - MenuItem::Action { - name: "Save All", - action: Box::new(workspace::SaveAll), - }, - MenuItem::Action { - name: "Close Editor", - action: Box::new(workspace::CloseActiveItem), - }, - MenuItem::Action { - name: "Close Window", - action: Box::new(workspace::CloseWindow), - }, + MenuItem::action("New", workspace::NewFile), + MenuItem::action("New Window", workspace::NewWindow), + MenuItem::separator(), + MenuItem::action("Open…", workspace::Open), + MenuItem::action("Open Recent...", recent_projects::OpenRecent), + MenuItem::separator(), + MenuItem::action("Add Folder to Project…", workspace::AddFolderToProject), + MenuItem::action("Save", workspace::Save), + MenuItem::action("Save As…", workspace::SaveAs), + MenuItem::action("Save All", workspace::SaveAll), + MenuItem::action("Close Editor", workspace::CloseActiveItem), + MenuItem::action("Close Window", workspace::CloseWindow), ], }, Menu { name: "Edit", items: vec![ - MenuItem::Action { - name: "Undo", - action: Box::new(editor::Undo), - }, - MenuItem::Action { - name: "Redo", - action: Box::new(editor::Redo), - }, - MenuItem::Separator, - MenuItem::Action { - name: "Cut", - action: Box::new(editor::Cut), - }, - MenuItem::Action { - name: "Copy", - action: Box::new(editor::Copy), - }, - MenuItem::Action { - name: "Paste", - action: Box::new(editor::Paste), - }, - MenuItem::Separator, - MenuItem::Action { - name: "Find", - action: Box::new(search::buffer_search::Deploy { focus: true }), - }, - MenuItem::Action { - name: "Find In Project", - action: Box::new(workspace::NewSearch), - }, - MenuItem::Separator, - MenuItem::Action { - name: "Toggle Line Comment", - action: Box::new(editor::ToggleComments::default()), - }, - MenuItem::Action { - name: "Emoji & Symbols", - action: Box::new(editor::ShowCharacterPalette), - }, + MenuItem::os_action("Undo", editor::Undo, OsAction::Undo), + MenuItem::os_action("Redo", editor::Redo, OsAction::Redo), + MenuItem::separator(), + MenuItem::os_action("Cut", editor::Cut, OsAction::Cut), + MenuItem::os_action("Copy", editor::Copy, OsAction::Copy), + MenuItem::os_action("Paste", editor::Paste, OsAction::Paste), + MenuItem::separator(), + MenuItem::action("Find", search::buffer_search::Deploy { focus: true }), + MenuItem::action("Find In Project", workspace::NewSearch), + MenuItem::separator(), + MenuItem::action("Toggle Line Comment", editor::ToggleComments::default()), + MenuItem::action("Emoji & Symbols", editor::ShowCharacterPalette), ], }, Menu { name: "Selection", items: vec![ - MenuItem::Action { - name: "Select All", - action: Box::new(editor::SelectAll), - }, - MenuItem::Action { - name: "Expand Selection", - action: Box::new(editor::SelectLargerSyntaxNode), - }, - MenuItem::Action { - name: "Shrink Selection", - action: Box::new(editor::SelectSmallerSyntaxNode), - }, - MenuItem::Separator, - MenuItem::Action { - name: "Add Cursor Above", - action: Box::new(editor::AddSelectionAbove), - }, - MenuItem::Action { - name: "Add Cursor Below", - action: Box::new(editor::AddSelectionBelow), - }, - MenuItem::Action { - name: "Select Next Occurrence", - action: Box::new(editor::SelectNext { + MenuItem::os_action("Select All", editor::SelectAll, OsAction::SelectAll), + MenuItem::action("Expand Selection", editor::SelectLargerSyntaxNode), + MenuItem::action("Shrink Selection", editor::SelectSmallerSyntaxNode), + MenuItem::separator(), + MenuItem::action("Add Cursor Above", editor::AddSelectionAbove), + MenuItem::action("Add Cursor Below", editor::AddSelectionBelow), + MenuItem::action( + "Select Next Occurrence", + editor::SelectNext { replace_newest: false, - }), - }, - MenuItem::Separator, - MenuItem::Action { - name: "Move Line Up", - action: Box::new(editor::MoveLineUp), - }, - MenuItem::Action { - name: "Move Line Down", - action: Box::new(editor::MoveLineDown), - }, - MenuItem::Action { - name: "Duplicate Selection", - action: Box::new(editor::DuplicateLine), - }, + }, + ), + MenuItem::separator(), + MenuItem::action("Move Line Up", editor::MoveLineUp), + MenuItem::action("Move Line Down", editor::MoveLineDown), + MenuItem::action("Duplicate Selection", editor::DuplicateLine), ], }, Menu { name: "View", items: vec![ - MenuItem::Action { - name: "Zoom In", - action: Box::new(super::IncreaseBufferFontSize), - }, - MenuItem::Action { - name: "Zoom Out", - action: Box::new(super::DecreaseBufferFontSize), - }, - MenuItem::Action { - name: "Reset Zoom", - action: Box::new(super::ResetBufferFontSize), - }, - MenuItem::Separator, - MenuItem::Action { - name: "Toggle Left Sidebar", - action: Box::new(workspace::ToggleLeftSidebar), - }, - MenuItem::Submenu(Menu { + MenuItem::action("Zoom In", super::IncreaseBufferFontSize), + MenuItem::action("Zoom Out", super::DecreaseBufferFontSize), + MenuItem::action("Reset Zoom", super::ResetBufferFontSize), + MenuItem::separator(), + MenuItem::action("Toggle Left Sidebar", workspace::ToggleLeftSidebar), + MenuItem::submenu(Menu { name: "Editor Layout", items: vec![ - MenuItem::Action { - name: "Split Up", - action: Box::new(workspace::SplitUp), - }, - MenuItem::Action { - name: "Split Down", - action: Box::new(workspace::SplitDown), - }, - MenuItem::Action { - name: "Split Left", - action: Box::new(workspace::SplitLeft), - }, - MenuItem::Action { - name: "Split Right", - action: Box::new(workspace::SplitRight), - }, + MenuItem::action("Split Up", workspace::SplitUp), + MenuItem::action("Split Down", workspace::SplitDown), + MenuItem::action("Split Left", workspace::SplitLeft), + MenuItem::action("Split Right", workspace::SplitRight), ], }), - MenuItem::Separator, - MenuItem::Action { - name: "Project Panel", - action: Box::new(project_panel::ToggleFocus), - }, - MenuItem::Action { - name: "Command Palette", - action: Box::new(command_palette::Toggle), - }, - MenuItem::Action { - name: "Diagnostics", - action: Box::new(diagnostics::Deploy), - }, - MenuItem::Separator, + MenuItem::separator(), + MenuItem::action("Project Panel", project_panel::ToggleFocus), + MenuItem::action("Command Palette", command_palette::Toggle), + MenuItem::action("Diagnostics", diagnostics::Deploy), + MenuItem::separator(), ], }, Menu { name: "Go", items: vec![ - MenuItem::Action { - name: "Back", - action: Box::new(workspace::GoBack { pane: None }), - }, - MenuItem::Action { - name: "Forward", - action: Box::new(workspace::GoForward { pane: None }), - }, - MenuItem::Separator, - MenuItem::Action { - name: "Go to File", - action: Box::new(file_finder::Toggle), - }, - MenuItem::Action { - name: "Go to Symbol in Project", - action: Box::new(project_symbols::Toggle), - }, - MenuItem::Action { - name: "Go to Symbol in Editor", - action: Box::new(outline::Toggle), - }, - MenuItem::Action { - name: "Go to Definition", - action: Box::new(editor::GoToDefinition), - }, - MenuItem::Action { - name: "Go to Type Definition", - action: Box::new(editor::GoToTypeDefinition), - }, - MenuItem::Action { - name: "Find All References", - action: Box::new(editor::FindAllReferences), - }, - MenuItem::Action { - name: "Go to Line/Column", - action: Box::new(go_to_line::Toggle), - }, - MenuItem::Separator, - MenuItem::Action { - name: "Next Problem", - action: Box::new(editor::GoToDiagnostic), - }, - MenuItem::Action { - name: "Previous Problem", - action: Box::new(editor::GoToPrevDiagnostic), - }, + MenuItem::action("Back", workspace::GoBack { pane: None }), + MenuItem::action("Forward", workspace::GoForward { pane: None }), + MenuItem::separator(), + MenuItem::action("Go to File", file_finder::Toggle), + MenuItem::action("Go to Symbol in Project", project_symbols::Toggle), + MenuItem::action("Go to Symbol in Editor", outline::Toggle), + MenuItem::action("Go to Definition", editor::GoToDefinition), + MenuItem::action("Go to Type Definition", editor::GoToTypeDefinition), + MenuItem::action("Find All References", editor::FindAllReferences), + MenuItem::action("Go to Line/Column", go_to_line::Toggle), + MenuItem::separator(), + MenuItem::action("Next Problem", editor::GoToDiagnostic), + MenuItem::action("Previous Problem", editor::GoToPrevDiagnostic), ], }, Menu { name: "Window", items: vec![ - MenuItem::Action { - name: "Minimize", - action: Box::new(super::Minimize), - }, - MenuItem::Action { - name: "Zoom", - action: Box::new(super::Zoom), - }, - MenuItem::Separator, + MenuItem::action("Minimize", super::Minimize), + MenuItem::action("Zoom", super::Zoom), + MenuItem::separator(), ], }, Menu { name: "Help", items: vec![ - MenuItem::Action { - name: "Command Palette", - action: Box::new(command_palette::Toggle), - }, - MenuItem::Separator, - MenuItem::Action { - name: "View Telemetry Log", - action: Box::new(crate::OpenTelemetryLog), - }, - MenuItem::Action { - name: "View Dependency Licenses", - action: Box::new(crate::OpenLicenses), - }, - MenuItem::Separator, - MenuItem::Action { - name: "Copy System Specs Into Clipboard", - action: Box::new(feedback::CopySystemSpecsIntoClipboard), - }, - MenuItem::Action { - name: "File Bug Report", - action: Box::new(feedback::FileBugReport), - }, - MenuItem::Action { - name: "Request Feature", - action: Box::new(feedback::RequestFeature), - }, - MenuItem::Separator, - MenuItem::Action { - name: "Documentation", - action: Box::new(crate::OpenBrowser { + MenuItem::action("Command Palette", command_palette::Toggle), + MenuItem::separator(), + MenuItem::action("View Telemetry Log", crate::OpenTelemetryLog), + MenuItem::action("View Dependency Licenses", crate::OpenLicenses), + MenuItem::separator(), + MenuItem::action( + "Copy System Specs Into Clipboard", + feedback::CopySystemSpecsIntoClipboard, + ), + MenuItem::action("File Bug Report", feedback::FileBugReport), + MenuItem::action("Request Feature", feedback::RequestFeature), + MenuItem::separator(), + MenuItem::action( + "Documentation", + crate::OpenBrowser { url: "https://zed.dev/docs".into(), - }), - }, - MenuItem::Action { - name: "Zed Twitter", - action: Box::new(crate::OpenBrowser { + }, + ), + MenuItem::action( + "Zed Twitter", + crate::OpenBrowser { url: "https://twitter.com/zeddotdev".into(), - }), - }, + }, + ), ], }, ] diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 754195e09943c59d61140430ca99550130614f44..19721547bda187bb3be53083cf52edfacf37e0cb 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -6,7 +6,7 @@ use anyhow::{anyhow, Context, Result}; use assets::Assets; use breadcrumbs::Breadcrumbs; pub use client; -use collab_ui::{CollabTitlebarItem, ToggleCollaborationMenu}; +use collab_ui::{CollabTitlebarItem, ToggleContactsMenu}; use collections::VecDeque; pub use editor; use editor::{Editor, MultiBuffer}; @@ -99,9 +99,7 @@ pub fn init(app_state: &Arc, cx: &mut gpui::MutableAppContext) { }, ); cx.add_action( - |workspace: &mut Workspace, - _: &ToggleCollaborationMenu, - cx: &mut ViewContext| { + |workspace: &mut Workspace, _: &ToggleContactsMenu, cx: &mut ViewContext| { if let Some(item) = workspace .titlebar_item() .and_then(|item| item.downcast::()) diff --git a/styles/.prettierignore b/styles/.prettierignore new file mode 100644 index 0000000000000000000000000000000000000000..04fe05da753a6c05c6d84733a26892bced1ba6a4 --- /dev/null +++ b/styles/.prettierignore @@ -0,0 +1,2 @@ +package-lock.json +package.json \ No newline at end of file diff --git a/styles/package-lock.json b/styles/package-lock.json index b0a904b11d8a463e790b332f393319403f61aafc..19741d62b38c98305ddebd3f5aa9f0f8182493db 100644 --- a/styles/package-lock.json +++ b/styles/package-lock.json @@ -9,67 +9,83 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@types/chroma-js": "^2.1.3", - "@types/node": "^17.0.23", + "@types/chroma-js": "^2.4.0", + "@types/node": "^18.14.1", + "bezier-easing": "^2.1.0", "case-anything": "^2.1.10", "chroma-js": "^2.4.2", + "deepmerge": "^4.3.0", "toml": "^3.0.0", - "ts-node": "^10.7.0" - } - }, - "node_modules/@cspotcode/source-map-consumer": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", - "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", - "engines": { - "node": ">= 12" + "ts-node": "^10.9.1" } }, "node_modules/@cspotcode/source-map-support": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", - "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", "dependencies": { - "@cspotcode/source-map-consumer": "0.8.0" + "@jridgewell/trace-mapping": "0.3.9" }, "engines": { "node": ">=12" } }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@tsconfig/node10": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", - "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==" + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==" }, "node_modules/@tsconfig/node12": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", - "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==" + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==" }, "node_modules/@tsconfig/node14": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", - "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==" }, "node_modules/@tsconfig/node16": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", - "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==" }, "node_modules/@types/chroma-js": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@types/chroma-js/-/chroma-js-2.1.3.tgz", - "integrity": "sha512-1xGPhoSGY1CPmXLCBcjVZSQinFjL26vlR8ZqprsBWiFyED4JacJJ9zHhh5aaUXqbY9B37mKQ73nlydVAXmr1+g==" + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/chroma-js/-/chroma-js-2.4.0.tgz", + "integrity": "sha512-JklMxityrwjBTjGY2anH8JaTx3yjRU3/sEHSblLH1ba5lqcSh1LnImXJZO5peJfXyqKYWjHTGy4s5Wz++hARrw==" }, "node_modules/@types/node": { - "version": "17.0.23", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.23.tgz", - "integrity": "sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw==" + "version": "18.14.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.1.tgz", + "integrity": "sha512-QH+37Qds3E0eDlReeboBxfHbX9omAcBCXEzswCu6jySP642jiM3cYSIkU/REqwhCUqXdonHFuBfJDiAJxMNhaQ==" }, "node_modules/acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", "bin": { "acorn": "bin/acorn" }, @@ -90,6 +106,11 @@ "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" }, + "node_modules/bezier-easing": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bezier-easing/-/bezier-easing-2.1.0.tgz", + "integrity": "sha512-gbIqZ/eslnUFC1tjEvtz0sgx+xTK20wDnYMIA27VA04R7w6xxXQPZDbibjA9DTWZRA2CXtwHykkVzlCaAJAZig==" + }, "node_modules/case-anything": { "version": "2.1.10", "resolved": "https://registry.npmjs.org/case-anything/-/case-anything-2.1.10.tgz", @@ -111,6 +132,14 @@ "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" }, + "node_modules/deepmerge": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.0.tgz", + "integrity": "sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -130,11 +159,11 @@ "integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==" }, "node_modules/ts-node": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz", - "integrity": "sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==", + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", "dependencies": { - "@cspotcode/source-map-support": "0.7.0", + "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", "@tsconfig/node12": "^1.0.7", "@tsconfig/node14": "^1.0.0", @@ -145,7 +174,7 @@ "create-require": "^1.1.0", "diff": "^4.0.1", "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.0", + "v8-compile-cache-lib": "^3.0.1", "yn": "3.1.1" }, "bin": { @@ -172,9 +201,9 @@ } }, "node_modules/typescript": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", - "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "peer": true, "bin": { "tsc": "bin/tsc", @@ -185,9 +214,9 @@ } }, "node_modules/v8-compile-cache-lib": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz", - "integrity": "sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" }, "node_modules/yn": { "version": "3.1.1", @@ -199,53 +228,67 @@ } }, "dependencies": { - "@cspotcode/source-map-consumer": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", - "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==" - }, "@cspotcode/source-map-support": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", - "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "requires": { + "@jridgewell/trace-mapping": "0.3.9" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + }, + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", "requires": { - "@cspotcode/source-map-consumer": "0.8.0" + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" } }, "@tsconfig/node10": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", - "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==" + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==" }, "@tsconfig/node12": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", - "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==" + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==" }, "@tsconfig/node14": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", - "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==" }, "@tsconfig/node16": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", - "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==" }, "@types/chroma-js": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@types/chroma-js/-/chroma-js-2.1.3.tgz", - "integrity": "sha512-1xGPhoSGY1CPmXLCBcjVZSQinFjL26vlR8ZqprsBWiFyED4JacJJ9zHhh5aaUXqbY9B37mKQ73nlydVAXmr1+g==" + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/chroma-js/-/chroma-js-2.4.0.tgz", + "integrity": "sha512-JklMxityrwjBTjGY2anH8JaTx3yjRU3/sEHSblLH1ba5lqcSh1LnImXJZO5peJfXyqKYWjHTGy4s5Wz++hARrw==" }, "@types/node": { - "version": "17.0.23", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.23.tgz", - "integrity": "sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw==" + "version": "18.14.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.1.tgz", + "integrity": "sha512-QH+37Qds3E0eDlReeboBxfHbX9omAcBCXEzswCu6jySP642jiM3cYSIkU/REqwhCUqXdonHFuBfJDiAJxMNhaQ==" }, "acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==" + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==" }, "acorn-walk": { "version": "8.2.0", @@ -257,6 +300,11 @@ "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" }, + "bezier-easing": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bezier-easing/-/bezier-easing-2.1.0.tgz", + "integrity": "sha512-gbIqZ/eslnUFC1tjEvtz0sgx+xTK20wDnYMIA27VA04R7w6xxXQPZDbibjA9DTWZRA2CXtwHykkVzlCaAJAZig==" + }, "case-anything": { "version": "2.1.10", "resolved": "https://registry.npmjs.org/case-anything/-/case-anything-2.1.10.tgz", @@ -272,6 +320,11 @@ "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" }, + "deepmerge": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.0.tgz", + "integrity": "sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og==" + }, "diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -288,11 +341,11 @@ "integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==" }, "ts-node": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz", - "integrity": "sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==", + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", "requires": { - "@cspotcode/source-map-support": "0.7.0", + "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", "@tsconfig/node12": "^1.0.7", "@tsconfig/node14": "^1.0.0", @@ -303,20 +356,20 @@ "create-require": "^1.1.0", "diff": "^4.0.1", "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.0", + "v8-compile-cache-lib": "^3.0.1", "yn": "3.1.1" } }, "typescript": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", - "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "peer": true }, "v8-compile-cache-lib": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz", - "integrity": "sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" }, "yn": { "version": "3.1.1", diff --git a/styles/package.json b/styles/package.json index 118269bc814b8474d35476792a2db8f26a23a626..ad4dfcf561ed3b1d47bd513c207c25a0c2c1b90c 100644 --- a/styles/package.json +++ b/styles/package.json @@ -10,11 +10,19 @@ "author": "", "license": "ISC", "dependencies": { - "@types/chroma-js": "^2.1.3", - "@types/node": "^17.0.23", + "@types/chroma-js": "^2.4.0", + "@types/node": "^18.14.1", + "bezier-easing": "^2.1.0", "case-anything": "^2.1.10", "chroma-js": "^2.4.2", + "deepmerge": "^4.3.0", "toml": "^3.0.0", - "ts-node": "^10.7.0" + "ts-node": "^10.9.1" + }, + "prettier": { + "semi": false, + "printWidth": 80, + "htmlWhitespaceSensitivity": "strict", + "tabWidth": 4 } } diff --git a/styles/src/buildLicenses.ts b/styles/src/buildLicenses.ts index 5026faef4ecac87c4d929829d8aa5e666968c81f..b1e130e80034703a494d3db55bab47dfd1fadedd 100644 --- a/styles/src/buildLicenses.ts +++ b/styles/src/buildLicenses.ts @@ -1,73 +1,87 @@ -import * as fs from "fs"; -import toml from "toml"; -import { - schemeMeta -} from "./colorSchemes"; -import { Meta } from "./themes/common/colorScheme"; -import https from "https"; -import crypto from "crypto"; +import * as fs from "fs" +import toml from "toml" +import { schemeMeta } from "./colorSchemes" +import { Meta } from "./themes/common/colorScheme" +import https from "https" +import crypto from "crypto" const accepted_licenses_file = `${__dirname}/../../script/licenses/zed-licenses.toml` // Use the cargo-about configuration file as the source of truth for supported licenses. function parseAcceptedToml(file: string): string[] { - let buffer = fs.readFileSync(file).toString(); + let buffer = fs.readFileSync(file).toString() - let obj = toml.parse(buffer); + let obj = toml.parse(buffer) - if (!Array.isArray(obj.accepted)) { - throw Error("Accepted license source is malformed") - } + if (!Array.isArray(obj.accepted)) { + throw Error("Accepted license source is malformed") + } - return obj.accepted + return obj.accepted } - function checkLicenses(schemeMeta: Meta[], licenses: string[]) { - for (let meta of schemeMeta) { - // FIXME: Add support for conjuctions and conditions - if (licenses.indexOf(meta.license.SPDX) < 0) { - throw Error(`License for theme ${meta.name} (${meta.license.SPDX}) is not supported`) + for (let meta of schemeMeta) { + // FIXME: Add support for conjuctions and conditions + if (licenses.indexOf(meta.license.SPDX) < 0) { + throw Error( + `License for theme ${meta.name} (${meta.license.SPDX}) is not supported` + ) + } } - } } +function getLicenseText( + schemeMeta: Meta[], + callback: (meta: Meta, license_text: string) => void +) { + for (let meta of schemeMeta) { + // The following copied from the example code on nodejs.org: + // https://nodejs.org/api/http.html#httpgetoptions-callback + https + .get(meta.license.https_url, (res) => { + const { statusCode } = res -function getLicenseText(schemeMeta: Meta[], callback: (meta: Meta, license_text: string) => void) { - for (let meta of schemeMeta) { - // The following copied from the example code on nodejs.org: - // https://nodejs.org/api/http.html#httpgetoptions-callback - https.get(meta.license.https_url, (res) => { - const { statusCode } = res; + if (statusCode < 200 || statusCode >= 300) { + throw new Error( + `Failed to fetch license for: ${meta.name}, Status Code: ${statusCode}` + ) + } - if (statusCode < 200 || statusCode >= 300) { - throw new Error(`Failed to fetch license for: ${meta.name}, Status Code: ${statusCode}`); - } - - res.setEncoding('utf8'); - let rawData = ''; - res.on('data', (chunk) => { rawData += chunk; }); - res.on('end', () => { - const hash = crypto.createHash('sha256').update(rawData).digest('hex'); - if (meta.license.license_checksum == hash) { - callback(meta, rawData) - } else { - throw Error(`Checksum for ${meta.name} did not match file downloaded from ${meta.license.https_url}`) - } - }); - }).on('error', (e) => { - throw e - }); - } + res.setEncoding("utf8") + let rawData = "" + res.on("data", (chunk) => { + rawData += chunk + }) + res.on("end", () => { + const hash = crypto + .createHash("sha256") + .update(rawData) + .digest("hex") + if (meta.license.license_checksum == hash) { + callback(meta, rawData) + } else { + throw Error( + `Checksum for ${meta.name} did not match file downloaded from ${meta.license.https_url}` + ) + } + }) + }) + .on("error", (e) => { + throw e + }) + } } function writeLicense(schemeMeta: Meta, text: String) { - process.stdout.write(`## [${schemeMeta.name}](${schemeMeta.url})\n\n${text}\n********************************************************************************\n\n`) + process.stdout.write( + `## [${schemeMeta.name}](${schemeMeta.url})\n\n${text}\n********************************************************************************\n\n` + ) } -const accepted_licenses = parseAcceptedToml(accepted_licenses_file); +const accepted_licenses = parseAcceptedToml(accepted_licenses_file) checkLicenses(schemeMeta, accepted_licenses) getLicenseText(schemeMeta, (meta, text) => { - writeLicense(meta, text) -}); + writeLicense(meta, text) +}) diff --git a/styles/src/buildThemes.ts b/styles/src/buildThemes.ts index 4bb7b8fc09dfa596bc5a12ae97e7a1cb5eae963a..2a63a407cc4f83dba146991a5edf3757abdfab65 100644 --- a/styles/src/buildThemes.ts +++ b/styles/src/buildThemes.ts @@ -1,50 +1,52 @@ -import * as fs from "fs"; -import { tmpdir } from "os"; -import * as path from "path"; -import colorSchemes, { - staffColorSchemes, -} from "./colorSchemes"; -import app from "./styleTree/app"; -import { ColorScheme } from "./themes/common/colorScheme"; -import snakeCase from "./utils/snakeCase"; +import * as fs from "fs" +import { tmpdir } from "os" +import * as path from "path" +import colorSchemes, { staffColorSchemes } from "./colorSchemes" +import app from "./styleTree/app" +import { ColorScheme } from "./themes/common/colorScheme" +import snakeCase from "./utils/snakeCase" const assetsDirectory = `${__dirname}/../../assets` -const themeDirectory = `${assetsDirectory}/themes`; -const staffDirectory = `${themeDirectory}/staff`; +const themeDirectory = `${assetsDirectory}/themes` +const staffDirectory = `${themeDirectory}/staff` -const tempDirectory = fs.mkdtempSync(path.join(tmpdir(), "build-themes")); +const tempDirectory = fs.mkdtempSync(path.join(tmpdir(), "build-themes")) // Clear existing themes function clearThemes(themeDirectory: string) { - if (!fs.existsSync(themeDirectory)) { - fs.mkdirSync(themeDirectory, { recursive: true }); - } else { - for (const file of fs.readdirSync(themeDirectory)) { - if (file.endsWith(".json")) { - const name = file.replace(/\.json$/, ""); - if (!colorSchemes.find((colorScheme) => colorScheme.name === name)) { - fs.unlinkSync(path.join(themeDirectory, file)); + if (!fs.existsSync(themeDirectory)) { + fs.mkdirSync(themeDirectory, { recursive: true }) + } else { + for (const file of fs.readdirSync(themeDirectory)) { + if (file.endsWith(".json")) { + const name = file.replace(/\.json$/, "") + if ( + !colorSchemes.find( + (colorScheme) => colorScheme.name === name + ) + ) { + fs.unlinkSync(path.join(themeDirectory, file)) + } + } } - } } - } } -clearThemes(themeDirectory); -clearThemes(staffDirectory); +clearThemes(themeDirectory) +clearThemes(staffDirectory) 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, `${colorScheme.name}.json`); - let outPath = path.join(outputDirectory, `${colorScheme.name}.json`); - fs.writeFileSync(tempPath, styleTreeJSON); - fs.renameSync(tempPath, outPath); - console.log(`- ${outPath} created`); - } + for (let colorScheme of colorSchemes) { + let styleTree = snakeCase(app(colorScheme)) + let styleTreeJSON = JSON.stringify(styleTree, null, 2) + 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`) + } } // Write new themes to theme directory -writeThemes(colorSchemes, themeDirectory); -writeThemes(staffColorSchemes, staffDirectory); +writeThemes(colorSchemes, themeDirectory) +writeThemes(staffColorSchemes, staffDirectory) diff --git a/styles/src/colorSchemes.ts b/styles/src/colorSchemes.ts index c7e1d4ead7ef8f426400add201351a19f7e9cfcc..4d2d7f6e0253c6b6dd8d6d579000f3d9cb53ad5f 100644 --- a/styles/src/colorSchemes.ts +++ b/styles/src/colorSchemes.ts @@ -1,54 +1,54 @@ -import fs from "fs"; -import path from "path"; -import { ColorScheme, Meta } from "./themes/common/colorScheme"; +import fs from "fs" +import path from "path" +import { ColorScheme, Meta } from "./themes/common/colorScheme" -const colorSchemes: ColorScheme[] = []; -export default colorSchemes; +const colorSchemes: ColorScheme[] = [] +export default colorSchemes -const schemeMeta: Meta[] = []; -export { schemeMeta }; +const schemeMeta: Meta[] = [] +export { schemeMeta } -const staffColorSchemes: ColorScheme[] = []; -export { staffColorSchemes }; +const staffColorSchemes: ColorScheme[] = [] +export { staffColorSchemes } -const experimentalColorSchemes: ColorScheme[] = []; -export { experimentalColorSchemes }; +const experimentalColorSchemes: ColorScheme[] = [] +export { experimentalColorSchemes } -const themes_directory = path.resolve(`${__dirname}/themes`); +const themes_directory = path.resolve(`${__dirname}/themes`) -function for_all_color_schemes_in(themesPath: string, callback: (module: any, path: string) => void) { - for (const fileName of fs.readdirSync(themesPath)) { - if (fileName == "template.ts") continue; - const filePath = path.join(themesPath, fileName); +function for_all_color_schemes_in( + themesPath: string, + callback: (module: any, path: string) => void +) { + 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); - callback(colorScheme, path.basename(filePath)); + if (fs.statSync(filePath).isFile()) { + const colorScheme = require(filePath) + callback(colorScheme, path.basename(filePath)) + } } - } } function fillColorSchemes(themesPath: string, colorSchemes: ColorScheme[]) { - for_all_color_schemes_in(themesPath, (colorScheme, _path) => { - if (colorScheme.dark) colorSchemes.push(colorScheme.dark); - if (colorScheme.light) colorSchemes.push(colorScheme.light); - }) + for_all_color_schemes_in(themesPath, (colorScheme, _path) => { + if (colorScheme.dark) colorSchemes.push(colorScheme.dark) + if (colorScheme.light) colorSchemes.push(colorScheme.light) + }) } -fillColorSchemes(themes_directory, colorSchemes); -fillColorSchemes( - path.resolve(`${themes_directory}/staff`), - staffColorSchemes -); +fillColorSchemes(themes_directory, colorSchemes) +fillColorSchemes(path.resolve(`${themes_directory}/staff`), staffColorSchemes) function fillMeta(themesPath: string, meta: Meta[]) { - for_all_color_schemes_in(themesPath, (colorScheme, path) => { - if (colorScheme.meta) { - meta.push(colorScheme.meta) - } else { - throw Error(`Public theme ${path} must have a meta field`) - } - }) + for_all_color_schemes_in(themesPath, (colorScheme, path) => { + if (colorScheme.meta) { + meta.push(colorScheme.meta) + } else { + throw Error(`Public theme ${path} must have a meta field`) + } + }) } -fillMeta(themes_directory, schemeMeta); +fillMeta(themes_directory, schemeMeta) diff --git a/styles/src/common.ts b/styles/src/common.ts index 9f636596d4bbf68bf7a613bf221ebbaeb90616f1..1b4fb6b37b5c4a660afe20595937a211d7704da5 100644 --- a/styles/src/common.ts +++ b/styles/src/common.ts @@ -1,66 +1,45 @@ export const fontFamilies = { - sans: "Zed Sans", - mono: "Zed Mono", -}; + sans: "Zed Sans", + mono: "Zed Mono", +} export const fontSizes = { - "3xs": 8, - "2xs": 10, - xs: 12, - sm: 14, - md: 16, - lg: 18, - xl: 20, -}; + "3xs": 8, + "2xs": 10, + xs: 12, + sm: 14, + md: 16, + lg: 18, + xl: 20, +} export type FontWeight = - | "thin" - | "extra_light" - | "light" - | "normal" - | "medium" - | "semibold" - | "bold" - | "extra_bold" - | "black"; + | "thin" + | "extra_light" + | "light" + | "normal" + | "medium" + | "semibold" + | "bold" + | "extra_bold" + | "black" export const fontWeights: { [key: string]: FontWeight } = { - thin: "thin", - extra_light: "extra_light", - light: "light", - normal: "normal", - medium: "medium", - semibold: "semibold", - bold: "bold", - extra_bold: "extra_bold", - black: "black", -}; + thin: "thin", + extra_light: "extra_light", + light: "light", + normal: "normal", + medium: "medium", + semibold: "semibold", + bold: "bold", + extra_bold: "extra_bold", + black: "black", +} export const sizes = { - px: 1, - xs: 2, - sm: 4, - md: 6, - lg: 8, - xl: 12, -}; - -// export const colors = { -// neutral: colorRamp(["white", "black"], { steps: 37, increment: 25 }), // (900/25) + 1 -// rose: colorRamp("#F43F5EFF"), -// red: colorRamp("#EF4444FF"), -// orange: colorRamp("#F97316FF"), -// amber: colorRamp("#F59E0BFF"), -// yellow: colorRamp("#EAB308FF"), -// lime: colorRamp("#84CC16FF"), -// green: colorRamp("#22C55EFF"), -// emerald: colorRamp("#10B981FF"), -// teal: colorRamp("#14B8A6FF"), -// cyan: colorRamp("#06BBD4FF"), -// sky: colorRamp("#0EA5E9FF"), -// blue: colorRamp("#3B82F6FF"), -// indigo: colorRamp("#6366F1FF"), -// violet: colorRamp("#8B5CF6FF"), -// purple: colorRamp("#A855F7FF"), -// fuschia: colorRamp("#D946E4FF"), -// pink: colorRamp("#EC4899FF"), -// } + px: 1, + xs: 2, + sm: 4, + md: 6, + lg: 8, + xl: 12, +} diff --git a/styles/src/styleTree/app.ts b/styles/src/styleTree/app.ts index 5d04050fe123f78c736df9a266a1ca8eeeb58824..dc57468df65f079af5eb5af7596823024b5d4df2 100644 --- a/styles/src/styleTree/app.ts +++ b/styles/src/styleTree/app.ts @@ -1,72 +1,72 @@ -import { text } from "./components"; -import contactFinder from "./contactFinder"; -import contactsPopover from "./contactsPopover"; -import commandPalette from "./commandPalette"; -import editor from "./editor"; -import projectPanel from "./projectPanel"; -import search from "./search"; -import picker from "./picker"; -import workspace from "./workspace"; -import contextMenu from "./contextMenu"; -import sharedScreen from "./sharedScreen"; -import projectDiagnostics from "./projectDiagnostics"; -import contactNotification from "./contactNotification"; -import updateNotification from "./updateNotification"; -import simpleMessageNotification from "./simpleMessageNotification"; -import projectSharedNotification from "./projectSharedNotification"; -import tooltip from "./tooltip"; -import terminal from "./terminal"; -import contactList from "./contactList"; -import incomingCallNotification from "./incomingCallNotification"; -import { ColorScheme } from "../themes/common/colorScheme"; -import feedback from "./feedback"; +import { text } from "./components" +import contactFinder from "./contactFinder" +import contactsPopover from "./contactsPopover" +import commandPalette from "./commandPalette" +import editor from "./editor" +import projectPanel from "./projectPanel" +import search from "./search" +import picker from "./picker" +import workspace from "./workspace" +import contextMenu from "./contextMenu" +import sharedScreen from "./sharedScreen" +import projectDiagnostics from "./projectDiagnostics" +import contactNotification from "./contactNotification" +import updateNotification from "./updateNotification" +import simpleMessageNotification from "./simpleMessageNotification" +import projectSharedNotification from "./projectSharedNotification" +import tooltip from "./tooltip" +import terminal from "./terminal" +import contactList from "./contactList" +import incomingCallNotification from "./incomingCallNotification" +import { ColorScheme } from "../themes/common/colorScheme" +import feedback from "./feedback" export default function app(colorScheme: ColorScheme): Object { - return { - meta: { - name: colorScheme.name, - isLight: colorScheme.isLight, - }, - 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), - sharedScreen: sharedScreen(colorScheme), - breadcrumbs: { - ...text(colorScheme.highest, "sans", "variant"), - padding: { - left: 6, - }, - }, - updateNotification: updateNotification(colorScheme), - simpleMessageNotification: simpleMessageNotification(colorScheme), - tooltip: tooltip(colorScheme), - terminal: terminal(colorScheme), - feedback: feedback(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"), - }, - }, - }; + return { + meta: { + name: colorScheme.name, + isLight: colorScheme.isLight, + }, + 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), + sharedScreen: sharedScreen(colorScheme), + breadcrumbs: { + ...text(colorScheme.highest, "sans", "variant"), + padding: { + left: 6, + }, + }, + updateNotification: updateNotification(colorScheme), + simpleMessageNotification: simpleMessageNotification(colorScheme), + tooltip: tooltip(colorScheme), + terminal: terminal(colorScheme), + feedback: feedback(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"), + }, + }, + } } diff --git a/styles/src/styleTree/commandPalette.ts b/styles/src/styleTree/commandPalette.ts index 0e0c10bed087be1351830bf3b243d638213dfb54..038a009007b5c3a7299f245d6a9a1690de0e2f65 100644 --- a/styles/src/styleTree/commandPalette.ts +++ b/styles/src/styleTree/commandPalette.ts @@ -1,30 +1,30 @@ -import { ColorScheme } from "../themes/common/colorScheme"; -import { withOpacity } from "../utils/color"; -import { text, background } from "./components"; +import { ColorScheme } from "../themes/common/colorScheme" +import { withOpacity } from "../utils/color" +import { text, background } from "./components" export default function commandPalette(colorScheme: ColorScheme) { - let layer = colorScheme.highest; - return { - keystrokeSpacing: 8, - key: { - text: text(layer, "mono", "variant", "default", { size: "xs" }), - cornerRadius: 2, - background: background(layer, "on"), - padding: { - top: 1, - bottom: 1, - left: 6, - right: 6, - }, - margin: { - top: 1, - bottom: 1, - left: 2, - }, - active: { - text: text(layer, "mono", "on", "default", { size: "xs" }), - background: withOpacity(background(layer, "on"), 0.2), - }, - }, - }; + let layer = colorScheme.highest + return { + keystrokeSpacing: 8, + key: { + text: text(layer, "mono", "variant", "default", { size: "xs" }), + cornerRadius: 2, + background: background(layer, "on"), + padding: { + top: 1, + bottom: 1, + left: 6, + right: 6, + }, + margin: { + top: 1, + bottom: 1, + left: 2, + }, + active: { + text: text(layer, "mono", "on", "default", { size: "xs" }), + background: withOpacity(background(layer, "on"), 0.2), + }, + }, + } } diff --git a/styles/src/styleTree/components.ts b/styles/src/styleTree/components.ts index 847b937416e0dfc5751bde28f1a84f4d53d8166a..edbced8323c390499388071f498ddfa912fc0aed 100644 --- a/styles/src/styleTree/components.ts +++ b/styles/src/styleTree/components.ts @@ -1,210 +1,210 @@ -import { fontFamilies, fontSizes, FontWeight } from "../common"; -import { Layer, Styles, StyleSets, Style } from "../themes/common/colorScheme"; +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); + 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); + return [ + "default", + "active", + "disabled", + "hovered", + "pressed", + "inverted", + ].includes(key) } function getStyle( - layer: Layer, - possibleStyleSetOrStyle?: any, - possibleStyle?: any + 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]; + 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, style?: Styles): string export function background( - layer: Layer, - styleSet?: StyleSets, - style?: Styles -): string; + layer: Layer, + styleSet?: StyleSets, + style?: Styles +): string export function background( - layer: Layer, - styleSetOrStyles?: StyleSets | Styles, - style?: Styles + layer: Layer, + styleSetOrStyles?: StyleSets | Styles, + style?: Styles ): string { - return getStyle(layer, styleSetOrStyles, style).background; + return getStyle(layer, styleSetOrStyles, style).background } -export function borderColor(layer: Layer, style?: Styles): string; +export function borderColor(layer: Layer, style?: Styles): string export function borderColor( - layer: Layer, - styleSet?: StyleSets, - style?: Styles -): string; + layer: Layer, + styleSet?: StyleSets, + style?: Styles +): string export function borderColor( - layer: Layer, - styleSetOrStyles?: StyleSets | Styles, - style?: Styles + layer: Layer, + styleSetOrStyles?: StyleSets | Styles, + style?: Styles ): string { - return getStyle(layer, styleSetOrStyles, style).border; + return getStyle(layer, styleSetOrStyles, style).border } -export function foreground(layer: Layer, style?: Styles): string; +export function foreground(layer: Layer, style?: Styles): string export function foreground( - layer: Layer, - styleSet?: StyleSets, - style?: Styles -): string; + layer: Layer, + styleSet?: StyleSets, + style?: Styles +): string export function foreground( - layer: Layer, - styleSetOrStyles?: StyleSets | Styles, - style?: Styles + layer: Layer, + styleSetOrStyles?: StyleSets | Styles, + style?: Styles ): string { - return getStyle(layer, styleSetOrStyles, style).foreground; + return getStyle(layer, styleSetOrStyles, style).foreground } interface Text { - family: keyof typeof fontFamilies; - color: string; - size: number; - weight?: FontWeight; - underline?: boolean; + family: keyof typeof fontFamilies + color: string + size: number + weight?: FontWeight + underline?: boolean } interface TextProperties { - size?: keyof typeof fontSizes; - weight?: FontWeight; - underline?: boolean; - color?: string; + size?: keyof typeof fontSizes + weight?: FontWeight + underline?: boolean + color?: string } export function text( - layer: Layer, - fontFamily: keyof typeof fontFamilies, - styleSet: StyleSets, - style: Styles, - properties?: TextProperties -): Text; + layer: Layer, + fontFamily: keyof typeof fontFamilies, + styleSet: StyleSets, + style: Styles, + properties?: TextProperties +): Text export function text( - layer: Layer, - fontFamily: keyof typeof fontFamilies, - styleSet: StyleSets, - properties?: TextProperties -): Text; + 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; + layer: Layer, + fontFamily: keyof typeof fontFamilies, + style: Styles, + properties?: TextProperties +): Text export function text( - layer: Layer, - fontFamily: keyof typeof fontFamilies, - properties?: TextProperties -): 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 + layer: Layer, + fontFamily: keyof typeof fontFamilies, + styleSetStyleOrProperties?: StyleSets | Styles | TextProperties, + styleOrProperties?: Styles | TextProperties, + properties?: TextProperties ) { - let style = getStyle(layer, styleSetStyleOrProperties, styleOrProperties); - - if (typeof styleSetStyleOrProperties === "object") { - properties = styleSetStyleOrProperties; - } - if (typeof styleOrProperties === "object") { - properties = styleOrProperties; - } - - let size = fontSizes[properties?.size || "sm"]; - let color = properties?.color || style.foreground; - - return { - family: fontFamilies[fontFamily], - ...properties, - color, - size, - }; + let style = getStyle(layer, styleSetStyleOrProperties, styleOrProperties) + + if (typeof styleSetStyleOrProperties === "object") { + properties = styleSetStyleOrProperties + } + if (typeof styleOrProperties === "object") { + properties = styleOrProperties + } + + let size = fontSizes[properties?.size || "sm"] + let color = properties?.color || style.foreground + + return { + family: fontFamilies[fontFamily], + ...properties, + color, + size, + } } export interface Border { - color: string; - width: number; - top?: boolean; - bottom?: boolean; - left?: boolean; - right?: boolean; - overlay?: boolean; + color: string + width: number + top?: boolean + bottom?: boolean + left?: boolean + right?: boolean + overlay?: boolean } export interface BorderProperties { - width?: number; - top?: boolean; - bottom?: boolean; - left?: boolean; - right?: boolean; - overlay?: boolean; + width?: number + top?: boolean + bottom?: boolean + left?: boolean + right?: boolean + overlay?: boolean } export function border( - layer: Layer, - styleSet: StyleSets, - style: Styles, - properties?: BorderProperties -): Border; + layer: Layer, + styleSet: StyleSets, + style: Styles, + properties?: BorderProperties +): Border export function border( - layer: Layer, - styleSet: StyleSets, - properties?: BorderProperties -): 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; + 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 + layer: Layer, + styleSetStyleOrProperties?: StyleSets | Styles | BorderProperties, + styleOrProperties?: Styles | BorderProperties, + properties?: BorderProperties ): Border { - let style = getStyle(layer, styleSetStyleOrProperties, styleOrProperties); - - if (typeof styleSetStyleOrProperties === "object") { - properties = styleSetStyleOrProperties; - } - if (typeof styleOrProperties === "object") { - properties = styleOrProperties; - } - - return { - color: style.border, - width: 1, - ...properties, - }; + let style = getStyle(layer, styleSetStyleOrProperties, styleOrProperties) + + if (typeof styleSetStyleOrProperties === "object") { + properties = styleSetStyleOrProperties + } + if (typeof styleOrProperties === "object") { + properties = styleOrProperties + } + + return { + color: style.border, + width: 1, + ...properties, + } } diff --git a/styles/src/styleTree/contactFinder.ts b/styles/src/styleTree/contactFinder.ts index d696f2c4b89f62c14f23f08011bae596a9af3193..42eb3b994980a6942f33d9a32b21c7fd33d859eb 100644 --- a/styles/src/styleTree/contactFinder.ts +++ b/styles/src/styleTree/contactFinder.ts @@ -1,70 +1,70 @@ -import picker from "./picker"; -import { ColorScheme } from "../themes/common/colorScheme"; -import { background, border, foreground, text } from "./components"; +import picker from "./picker" +import { ColorScheme } from "../themes/common/colorScheme" +import { background, border, foreground, text } from "./components" export default function contactFinder(colorScheme: ColorScheme) { - let layer = colorScheme.middle; + let layer = colorScheme.middle - const sideMargin = 6; - const contactButton = { - background: background(layer, "variant"), - color: foreground(layer, "variant"), - iconWidth: 8, - buttonWidth: 16, - cornerRadius: 8, - }; + const sideMargin = 6 + const contactButton = { + background: background(layer, "variant"), + color: foreground(layer, "variant"), + iconWidth: 8, + buttonWidth: 16, + cornerRadius: 8, + } - const pickerStyle = picker(colorScheme); - const pickerInput = { - background: background(layer, "on"), - cornerRadius: 6, - text: text(layer, "mono",), - placeholderText: text(layer, "mono", "on", "disabled", { size: "xs" }), - selection: colorScheme.players[0], - border: border(layer), - padding: { - bottom: 4, - left: 8, - right: 8, - top: 4, - }, - margin: { - left: sideMargin, - right: sideMargin, + const pickerStyle = picker(colorScheme) + const pickerInput = { + background: background(layer, "on"), + cornerRadius: 6, + text: text(layer, "mono"), + placeholderText: text(layer, "mono", "on", "disabled", { size: "xs" }), + selection: colorScheme.players[0], + border: border(layer), + padding: { + bottom: 4, + left: 8, + right: 8, + top: 4, + }, + margin: { + left: sideMargin, + right: sideMargin, + }, } - }; - return { - picker: { - emptyContainer: {}, - item: { - ...pickerStyle.item, - margin: { left: sideMargin, right: sideMargin }, - }, - noMatches: pickerStyle.noMatches, - inputEditor: pickerInput, - emptyInputEditor: pickerInput - }, - rowHeight: 28, - contactAvatar: { - cornerRadius: 10, - width: 18, - }, - contactUsername: { - padding: { - left: 8, - }, - }, - contactButton: { - ...contactButton, - hover: { - background: background(layer, "variant", "hovered"), - }, - }, - disabledContactButton: { - ...contactButton, - background: background(layer, "disabled"), - color: foreground(layer, "disabled"), - }, - }; + return { + picker: { + emptyContainer: {}, + item: { + ...pickerStyle.item, + margin: { left: sideMargin, right: sideMargin }, + }, + noMatches: pickerStyle.noMatches, + inputEditor: pickerInput, + emptyInputEditor: pickerInput, + }, + rowHeight: 28, + contactAvatar: { + cornerRadius: 10, + width: 18, + }, + contactUsername: { + padding: { + left: 8, + }, + }, + contactButton: { + ...contactButton, + hover: { + background: background(layer, "variant", "hovered"), + }, + }, + disabledContactButton: { + ...contactButton, + background: background(layer, "disabled"), + color: foreground(layer, "disabled"), + }, + } } diff --git a/styles/src/styleTree/contactList.ts b/styles/src/styleTree/contactList.ts index 456786c4be2a44f32d2cba19b9ddbc72f866fdb6..65dd1c8654924296d030ba5e4f8af45eef45a261 100644 --- a/styles/src/styleTree/contactList.ts +++ b/styles/src/styleTree/contactList.ts @@ -1,186 +1,182 @@ -import { ColorScheme } from "../themes/common/colorScheme"; -import { - background, - border, - borderColor, - foreground, - text, -} from "./components"; +import { ColorScheme } from "../themes/common/colorScheme" +import { background, border, borderColor, foreground, text } from "./components" export default function contactsPanel(colorScheme: ColorScheme) { - const nameMargin = 8; - const sidePadding = 12; + const nameMargin = 8 + const sidePadding = 12 - let layer = colorScheme.middle; + let layer = colorScheme.middle - const contactButton = { - background: background(layer, "on"), - color: foreground(layer, "on"), - iconWidth: 8, - buttonWidth: 16, - cornerRadius: 8, - }; - const projectRow = { - guestAvatarSpacing: 4, - height: 24, - guestAvatar: { - cornerRadius: 8, - width: 14, - }, - name: { - ...text(layer, "mono", { size: "sm" }), - margin: { - left: nameMargin, - right: 6, - }, - }, - guests: { - margin: { - left: nameMargin, - right: nameMargin, - }, - }, - padding: { - left: sidePadding, - right: sidePadding, - }, - }; + const contactButton = { + background: background(layer, "on"), + color: foreground(layer, "on"), + iconWidth: 8, + buttonWidth: 16, + cornerRadius: 8, + } + const projectRow = { + guestAvatarSpacing: 4, + height: 24, + guestAvatar: { + cornerRadius: 8, + width: 14, + }, + name: { + ...text(layer, "mono", { size: "sm" }), + margin: { + left: nameMargin, + right: 6, + }, + }, + guests: { + margin: { + left: nameMargin, + right: nameMargin, + }, + }, + padding: { + left: sidePadding, + right: sidePadding, + }, + } - return { - background: background(layer), - padding: { top: 12, bottom: 0 }, - userQueryEditor: { - background: background(layer, "on"), - cornerRadius: 6, - text: text(layer, "mono", "on"), - placeholderText: text(layer, "mono", "on", "disabled", { size: "xs" }), - selection: colorScheme.players[0], - border: border(layer, "on"), - padding: { - bottom: 4, - left: 8, - right: 8, - top: 4, - }, - margin: { - left: 6, - }, - }, - userQueryEditorHeight: 33, - addContactButton: { - margin: { left: 6, right: 12 }, - color: foreground(layer, "on"), - buttonWidth: 28, - iconWidth: 16, - }, - rowHeight: 28, - sectionIconSize: 8, - headerRow: { - ...text(layer, "mono", { size: "sm" }), - margin: { top: 14 }, - padding: { - left: sidePadding, - right: sidePadding, - }, - active: { - ...text(layer, "mono", "active", { size: "sm" }), - background: background(layer, "active"), - }, - }, - leaveCall: { - background: background(layer), - border: border(layer), - cornerRadius: 6, - margin: { - top: 1, - }, - padding: { - top: 1, - bottom: 1, - left: 7, - right: 7, - }, - ...text(layer, "sans", "variant", { size: "xs" }), - hover: { - ...text(layer, "sans", "hovered", { size: "xs" }), - background: background(layer, "hovered"), - border: border(layer, "hovered"), - }, - }, - contactRow: { - padding: { - left: sidePadding, - right: sidePadding, - }, - active: { - background: background(layer, "active"), - }, - }, - contactAvatar: { - cornerRadius: 10, - width: 18, - }, - contactStatusFree: { - cornerRadius: 4, - padding: 4, - margin: { top: 12, left: 12 }, - background: foreground(layer, "positive"), - }, - contactStatusBusy: { - cornerRadius: 4, - padding: 4, - margin: { top: 12, left: 12 }, - background: foreground(layer, "negative"), - }, - contactUsername: { - ...text(layer, "mono", { size: "sm" }), - margin: { - left: nameMargin, - }, - }, - contactButtonSpacing: nameMargin, - contactButton: { - ...contactButton, - hover: { - background: background(layer, "hovered"), - }, - }, - disabledButton: { - ...contactButton, - background: background(layer, "on"), - color: foreground(layer, "on"), - }, - callingIndicator: { - ...text(layer, "mono", "variant", { size: "xs" }), - }, - treeBranch: { - color: borderColor(layer), - width: 1, - hover: { - color: borderColor(layer), - }, - active: { - color: borderColor(layer), - }, - }, - projectRow: { - ...projectRow, - background: background(layer), - icon: { - margin: { left: nameMargin }, - color: foreground(layer, "variant"), - width: 12, - }, - name: { - ...projectRow.name, - ...text(layer, "mono", { size: "sm" }), - }, - hover: { - background: background(layer, "hovered"), - }, - active: { - background: background(layer, "active"), - }, - }, - }; + return { + background: background(layer), + padding: { top: 12, bottom: 0 }, + userQueryEditor: { + background: background(layer, "on"), + cornerRadius: 6, + text: text(layer, "mono", "on"), + placeholderText: text(layer, "mono", "on", "disabled", { + size: "xs", + }), + selection: colorScheme.players[0], + border: border(layer, "on"), + padding: { + bottom: 4, + left: 8, + right: 8, + top: 4, + }, + margin: { + left: 6, + }, + }, + userQueryEditorHeight: 33, + addContactButton: { + margin: { left: 6, right: 12 }, + color: foreground(layer, "on"), + buttonWidth: 28, + iconWidth: 16, + }, + rowHeight: 28, + sectionIconSize: 8, + headerRow: { + ...text(layer, "mono", { size: "sm" }), + margin: { top: 14 }, + padding: { + left: sidePadding, + right: sidePadding, + }, + active: { + ...text(layer, "mono", "active", { size: "sm" }), + background: background(layer, "active"), + }, + }, + leaveCall: { + background: background(layer), + border: border(layer), + cornerRadius: 6, + margin: { + top: 1, + }, + padding: { + top: 1, + bottom: 1, + left: 7, + right: 7, + }, + ...text(layer, "sans", "variant", { size: "xs" }), + hover: { + ...text(layer, "sans", "hovered", { size: "xs" }), + background: background(layer, "hovered"), + border: border(layer, "hovered"), + }, + }, + contactRow: { + padding: { + left: sidePadding, + right: sidePadding, + }, + active: { + background: background(layer, "active"), + }, + }, + contactAvatar: { + cornerRadius: 10, + width: 18, + }, + contactStatusFree: { + cornerRadius: 4, + padding: 4, + margin: { top: 12, left: 12 }, + background: foreground(layer, "positive"), + }, + contactStatusBusy: { + cornerRadius: 4, + padding: 4, + margin: { top: 12, left: 12 }, + background: foreground(layer, "negative"), + }, + contactUsername: { + ...text(layer, "mono", { size: "sm" }), + margin: { + left: nameMargin, + }, + }, + contactButtonSpacing: nameMargin, + contactButton: { + ...contactButton, + hover: { + background: background(layer, "hovered"), + }, + }, + disabledButton: { + ...contactButton, + background: background(layer, "on"), + color: foreground(layer, "on"), + }, + callingIndicator: { + ...text(layer, "mono", "variant", { size: "xs" }), + }, + treeBranch: { + color: borderColor(layer), + width: 1, + hover: { + color: borderColor(layer), + }, + active: { + color: borderColor(layer), + }, + }, + projectRow: { + ...projectRow, + background: background(layer), + icon: { + margin: { left: nameMargin }, + color: foreground(layer, "variant"), + width: 12, + }, + name: { + ...projectRow.name, + ...text(layer, "mono", { size: "sm" }), + }, + hover: { + background: background(layer, "hovered"), + }, + active: { + background: background(layer, "active"), + }, + }, + } } diff --git a/styles/src/styleTree/contactNotification.ts b/styles/src/styleTree/contactNotification.ts index e3f15baa32285ce202a279736f1d5f9ed45850cb..186f7e8685fc5a823c1444ee759d4eb9926e02c6 100644 --- a/styles/src/styleTree/contactNotification.ts +++ b/styles/src/styleTree/contactNotification.ts @@ -1,45 +1,45 @@ -import { ColorScheme } from "../themes/common/colorScheme"; -import { background, foreground, text } from "./components"; +import { ColorScheme } from "../themes/common/colorScheme" +import { background, foreground, text } from "./components" -const avatarSize = 12; -const headerPadding = 8; +const avatarSize = 12 +const headerPadding = 8 export default function contactNotification(colorScheme: ColorScheme): Object { - let layer = colorScheme.lowest; - return { - headerAvatar: { - height: avatarSize, - width: avatarSize, - cornerRadius: 6, - }, - headerMessage: { - ...text(layer, "sans", { size: "xs" }), - margin: { left: headerPadding, right: headerPadding }, - }, - headerHeight: 18, - bodyMessage: { - ...text(layer, "sans", { size: "xs" }), - margin: { left: avatarSize + headerPadding, top: 6, bottom: 6 }, - }, - button: { - ...text(layer, "sans", "on", { size: "xs" }), - background: background(layer, "on"), - padding: 4, - cornerRadius: 6, - margin: { left: 6 }, - hover: { - background: background(layer, "on", "hovered"), - }, - }, - dismissButton: { - color: foreground(layer, "variant"), - iconWidth: 8, - iconHeight: 8, - buttonWidth: 8, - buttonHeight: 8, - hover: { - color: foreground(layer, "hovered"), - }, - }, - }; + let layer = colorScheme.lowest + return { + headerAvatar: { + height: avatarSize, + width: avatarSize, + cornerRadius: 6, + }, + headerMessage: { + ...text(layer, "sans", { size: "xs" }), + margin: { left: headerPadding, right: headerPadding }, + }, + headerHeight: 18, + bodyMessage: { + ...text(layer, "sans", { size: "xs" }), + margin: { left: avatarSize + headerPadding, top: 6, bottom: 6 }, + }, + button: { + ...text(layer, "sans", "on", { size: "xs" }), + background: background(layer, "on"), + padding: 4, + cornerRadius: 6, + margin: { left: 6 }, + hover: { + background: background(layer, "on", "hovered"), + }, + }, + dismissButton: { + color: foreground(layer, "variant"), + iconWidth: 8, + iconHeight: 8, + buttonWidth: 8, + buttonHeight: 8, + hover: { + color: foreground(layer, "hovered"), + }, + }, + } } diff --git a/styles/src/styleTree/contactsPopover.ts b/styles/src/styleTree/contactsPopover.ts index 50a82517370b258c072f09caf075fa0ee29de1d3..9a299d20d68f28e56638d59492354c7f0dcc6102 100644 --- a/styles/src/styleTree/contactsPopover.ts +++ b/styles/src/styleTree/contactsPopover.ts @@ -1,29 +1,29 @@ -import { ColorScheme } from "../themes/common/colorScheme"; -import { background, border, text } from "./components"; +import { ColorScheme } from "../themes/common/colorScheme" +import { background, border, text } from "./components" export default function contactsPopover(colorScheme: ColorScheme) { - let layer = colorScheme.middle; - const sidePadding = 12; - return { - background: background(layer), - cornerRadius: 6, - padding: { top: 6 }, - margin: { top: -6 }, - shadow: colorScheme.popoverShadow, - border: border(layer), - width: 300, - height: 400, - inviteRowHeight: 28, - inviteRow: { - padding: { - left: sidePadding, - right: sidePadding, - }, - border: border(layer, { top: true }), - text: text(layer, "sans", "variant", { size: "sm" }), - hover: { - text: text(layer, "sans", "hovered", { size: "sm" }), - }, - }, - } + let layer = colorScheme.middle + const sidePadding = 12 + return { + background: background(layer), + cornerRadius: 6, + padding: { top: 6 }, + margin: { top: -6 }, + shadow: colorScheme.popoverShadow, + border: border(layer), + width: 300, + height: 400, + inviteRowHeight: 28, + inviteRow: { + padding: { + left: sidePadding, + right: sidePadding, + }, + border: border(layer, { top: true }), + text: text(layer, "sans", "variant", { size: "sm" }), + hover: { + text: text(layer, "sans", "hovered", { size: "sm" }), + }, + }, + } } diff --git a/styles/src/styleTree/contextMenu.ts b/styles/src/styleTree/contextMenu.ts index 49f3a3b472537ce50a0200ddd6f0ed2ced0d2e4d..30f44a63141a70edd01998057385dfcd7f15adfc 100644 --- a/styles/src/styleTree/contextMenu.ts +++ b/styles/src/styleTree/contextMenu.ts @@ -1,41 +1,44 @@ -import { ColorScheme } from "../themes/common/colorScheme"; -import { background, border, borderColor, text } from "./components"; +import { ColorScheme } from "../themes/common/colorScheme" +import { background, border, borderColor, text } from "./components" export default function contextMenu(colorScheme: ColorScheme) { - let layer = colorScheme.middle; - return { - background: background(layer), - cornerRadius: 10, - padding: 4, - shadow: colorScheme.popoverShadow, - border: border(layer), - keystrokeMargin: 30, - item: { - iconSpacing: 8, - iconWidth: 14, - padding: { left: 6, right: 6, top: 2, bottom: 2 }, - cornerRadius: 6, - label: text(layer, "sans", { size: "sm" }), - keystroke: { - ...text(layer, "sans", "variant", { size: "sm", weight: "bold" }), - padding: { left: 3, right: 3 }, - }, - hover: { - background: background(layer, "hovered"), - label: text(layer, "sans", "hovered", { size: "sm" }), - }, - active: { - background: background(layer, "active"), - label: text(layer, "sans", "active", { size: "sm" }), - }, - activeHover: { - background: background(layer, "active"), - label: text(layer, "sans", "active", { size: "sm" }), - }, - }, - separator: { - background: borderColor(layer), - margin: { top: 2, bottom: 2 }, - }, - }; + let layer = colorScheme.middle + return { + background: background(layer), + cornerRadius: 10, + padding: 4, + shadow: colorScheme.popoverShadow, + border: border(layer), + keystrokeMargin: 30, + item: { + iconSpacing: 8, + iconWidth: 14, + padding: { left: 6, right: 6, top: 2, bottom: 2 }, + cornerRadius: 6, + label: text(layer, "sans", { size: "sm" }), + keystroke: { + ...text(layer, "sans", "variant", { + size: "sm", + weight: "bold", + }), + padding: { left: 3, right: 3 }, + }, + hover: { + background: background(layer, "hovered"), + label: text(layer, "sans", "hovered", { size: "sm" }), + }, + active: { + background: background(layer, "active"), + label: text(layer, "sans", "active", { size: "sm" }), + }, + activeHover: { + background: background(layer, "active"), + label: text(layer, "sans", "active", { size: "sm" }), + }, + }, + separator: { + background: borderColor(layer), + margin: { top: 2, bottom: 2 }, + }, + } } diff --git a/styles/src/styleTree/editor.ts b/styles/src/styleTree/editor.ts index e96cab0316a4116d7738efeb7f2336b78cb6c532..6dde5363fcafce9ec0f063f1e0241192682328b2 100644 --- a/styles/src/styleTree/editor.ts +++ b/styles/src/styleTree/editor.ts @@ -1,285 +1,317 @@ -import { fontWeights } from "../common"; -import { withOpacity } from "../utils/color"; -import { ColorScheme, Layer, StyleSets } from "../themes/common/colorScheme"; +import { fontWeights } from "../common" +import { withOpacity } from "../utils/color" import { - background, - border, - borderColor, - foreground, - text, -} from "./components"; -import hoverPopover from "./hoverPopover"; + ColorScheme, + Layer, + StyleSets, + Syntax, + ThemeSyntax, +} from "../themes/common/colorScheme" +import { background, border, borderColor, foreground, text } from "./components" +import hoverPopover from "./hoverPopover" + +import deepmerge from "deepmerge" export default function editor(colorScheme: ColorScheme) { - let layer = colorScheme.highest; + let layer = colorScheme.highest - const autocompleteItem = { - cornerRadius: 6, - padding: { - bottom: 2, - left: 6, - right: 6, - top: 2, - }, - }; + const autocompleteItem = { + cornerRadius: 6, + padding: { + bottom: 2, + left: 6, + right: 6, + top: 2, + }, + } - function diagnostic(layer: Layer, styleSet: StyleSets) { - return { - textScaleFactor: 0.857, - header: { - border: border(layer, { - top: true, - }), - }, - message: { - text: text(layer, "sans", styleSet, "default", { size: "sm" }), - highlightText: text(layer, "sans", styleSet, "default", { - size: "sm", - weight: "bold", - }), - }, - }; - } + function diagnostic(layer: Layer, styleSet: StyleSets) { + return { + textScaleFactor: 0.857, + header: { + border: border(layer, { + top: true, + }), + }, + message: { + text: text(layer, "sans", styleSet, "default", { size: "sm" }), + highlightText: text(layer, "sans", styleSet, "default", { + size: "sm", + weight: "bold", + }), + }, + } + } + + const defaultSyntax: Syntax = { + primary: { + color: colorScheme.ramps.neutral(1).hex(), + weight: fontWeights.normal, + }, + "variable.special": { + // Highlights for self, this, etc + color: colorScheme.ramps.blue(0.7).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, + }, + } + + function createSyntax(colorScheme: ColorScheme): Syntax { + if (!colorScheme.syntax) { + return defaultSyntax + } - const syntax = { - primary: { - color: colorScheme.ramps.neutral(1).hex(), - weight: fontWeights.normal, - }, - "variable.special": { - // Highlights for self, this, etc - color: colorScheme.ramps.blue(0.7).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 deepmerge>( + defaultSyntax, + colorScheme.syntax, + { + arrayMerge: (destinationArray, sourceArray) => [ + ...destinationArray, + ...sourceArray, + ], + } + ) + } - return { - textColor: syntax.primary.color, - background: background(layer), - activeLineBackground: withOpacity(background(layer, "on"), 0.75), - highlightedLineBackground: background(layer, "on"), - codeActions: { - indicator: foreground(layer, "variant"), - verticalScale: 0.55, - }, - diff: { - deleted: foreground(layer, "negative"), - modified: foreground(layer, "warning"), - inserted: foreground(layer, "positive"), - removedWidthEm: 0.275, - widthEm: 0.16, - cornerRadius: 0.05, - }, - /** Highlights matching occurences of what is under the cursor - * as well as matched brackets - */ - documentHighlightReadBackground: withOpacity(foreground(layer, "accent"), 0.1), - 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, - lineNumber: withOpacity(foreground(layer), 0.35), - lineNumberActive: foreground(layer), - renameFade: 0.6, - unnecessaryCodeFade: 0.5, - selection: colorScheme.players[0], - guestSelections: [ - colorScheme.players[1], - colorScheme.players[2], - colorScheme.players[3], - colorScheme.players[4], - colorScheme.players[5], - colorScheme.players[6], - colorScheme.players[7], - ], - autocomplete: { - background: background(colorScheme.middle), - cornerRadius: 8, - padding: 4, - margin: { - left: -14, - }, - border: border(colorScheme.middle), - shadow: colorScheme.popoverShadow, - matchHighlight: foreground(colorScheme.middle, "accent"), - item: autocompleteItem, - hoveredItem: { - ...autocompleteItem, - matchHighlight: foreground(colorScheme.middle, "accent", "hovered"), - background: background(colorScheme.middle, "hovered"), - }, - selectedItem: { - ...autocompleteItem, - matchHighlight: foreground(colorScheme.middle, "accent", "active"), - background: background(colorScheme.middle, "active"), - }, - }, - diagnosticHeader: { - background: background(colorScheme.middle), - iconWidthFactor: 1.5, - textScaleFactor: 0.857, - border: border(colorScheme.middle, { - bottom: true, - top: true, - }), - code: { - ...text(colorScheme.middle, "mono", { size: "sm" }), - margin: { - left: 10, - }, - }, - message: { - highlightText: text(colorScheme.middle, "sans", { - size: "sm", - weight: "bold", - }), - text: text(colorScheme.middle, "sans", { size: "sm" }), - }, - }, - diagnosticPathHeader: { - background: background(colorScheme.middle), - textScaleFactor: 0.857, - filename: text(colorScheme.middle, "mono", { size: "sm" }), - path: { - ...text(colorScheme.middle, "mono", { size: "sm" }), - margin: { - left: 12, - }, - }, - }, - 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: syntax.linkUri.color, - underline: syntax.linkUri.underline, - }, - jumpIcon: { - color: foreground(layer, "on"), - iconWidth: 20, - buttonWidth: 20, - cornerRadius: 6, - padding: { - top: 6, - bottom: 6, - left: 6, - right: 6, - }, - hover: { - background: background(layer, "on", "hovered"), - }, - }, - scrollbar: { - width: 12, - minHeightFactor: 1.0, - track: { - border: border(layer, "variant", { left: true }), - }, - thumb: { - background: withOpacity(background(layer, "inverted"), 0.4), - border: { - width: 1, - color: borderColor(layer, "variant"), - }, - }, - }, - compositionMark: { - underline: { - thickness: 1.0, - color: borderColor(layer), - }, - }, - syntax, - }; + const syntax = createSyntax(colorScheme) + + return { + textColor: syntax.primary.color, + background: background(layer), + activeLineBackground: withOpacity(background(layer, "on"), 0.75), + highlightedLineBackground: background(layer, "on"), + codeActions: { + indicator: foreground(layer, "variant"), + verticalScale: 0.55, + }, + diff: { + deleted: foreground(layer, "negative"), + modified: foreground(layer, "warning"), + inserted: foreground(layer, "positive"), + removedWidthEm: 0.275, + widthEm: 0.16, + cornerRadius: 0.05, + }, + /** Highlights matching occurences of what is under the cursor + * as well as matched brackets + */ + documentHighlightReadBackground: withOpacity( + foreground(layer, "accent"), + 0.1 + ), + 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, + lineNumber: withOpacity(foreground(layer), 0.35), + lineNumberActive: foreground(layer), + renameFade: 0.6, + unnecessaryCodeFade: 0.5, + selection: colorScheme.players[0], + guestSelections: [ + colorScheme.players[1], + colorScheme.players[2], + colorScheme.players[3], + colorScheme.players[4], + colorScheme.players[5], + colorScheme.players[6], + colorScheme.players[7], + ], + autocomplete: { + background: background(colorScheme.middle), + cornerRadius: 8, + padding: 4, + margin: { + left: -14, + }, + border: border(colorScheme.middle), + shadow: colorScheme.popoverShadow, + matchHighlight: foreground(colorScheme.middle, "accent"), + item: autocompleteItem, + hoveredItem: { + ...autocompleteItem, + matchHighlight: foreground( + colorScheme.middle, + "accent", + "hovered" + ), + background: background(colorScheme.middle, "hovered"), + }, + selectedItem: { + ...autocompleteItem, + matchHighlight: foreground( + colorScheme.middle, + "accent", + "active" + ), + background: background(colorScheme.middle, "active"), + }, + }, + diagnosticHeader: { + background: background(colorScheme.middle), + iconWidthFactor: 1.5, + textScaleFactor: 0.857, + border: border(colorScheme.middle, { + bottom: true, + top: true, + }), + code: { + ...text(colorScheme.middle, "mono", { size: "sm" }), + margin: { + left: 10, + }, + }, + message: { + highlightText: text(colorScheme.middle, "sans", { + size: "sm", + weight: "bold", + }), + text: text(colorScheme.middle, "sans", { size: "sm" }), + }, + }, + diagnosticPathHeader: { + background: background(colorScheme.middle), + textScaleFactor: 0.857, + filename: text(colorScheme.middle, "mono", { size: "sm" }), + path: { + ...text(colorScheme.middle, "mono", { size: "sm" }), + margin: { + left: 12, + }, + }, + }, + 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: syntax.linkUri.color, + underline: syntax.linkUri.underline, + }, + jumpIcon: { + color: foreground(layer, "on"), + iconWidth: 20, + buttonWidth: 20, + cornerRadius: 6, + padding: { + top: 6, + bottom: 6, + left: 6, + right: 6, + }, + hover: { + background: background(layer, "on", "hovered"), + }, + }, + scrollbar: { + width: 12, + minHeightFactor: 1.0, + track: { + border: border(layer, "variant", { left: true }), + }, + thumb: { + background: withOpacity(background(layer, "inverted"), 0.4), + border: { + width: 1, + color: borderColor(layer, "variant"), + }, + }, + }, + compositionMark: { + underline: { + thickness: 1.0, + color: borderColor(layer), + }, + }, + syntax, + } } diff --git a/styles/src/styleTree/feedback.ts b/styles/src/styleTree/feedback.ts index 8e3c6cda97501a5fbb220a39ff13cc70b1bd3763..d7946744397f629324af4c832e1c704c58cbe6c8 100644 --- a/styles/src/styleTree/feedback.ts +++ b/styles/src/styleTree/feedback.ts @@ -1,39 +1,38 @@ - -import { ColorScheme } from "../themes/common/colorScheme"; -import { background, border, text } from "./components"; +import { ColorScheme } from "../themes/common/colorScheme" +import { background, border, text } from "./components" export default function feedback(colorScheme: ColorScheme) { - let layer = colorScheme.highest; + let layer = colorScheme.highest - return { - submit_button: { - ...text(layer, "mono", "on"), - background: background(layer, "on"), - cornerRadius: 6, - border: border(layer, "on"), - margin: { - right: 4, - }, - padding: { - bottom: 2, - left: 10, - right: 10, - top: 2, - }, - clicked: { - ...text(layer, "mono", "on", "pressed"), - background: background(layer, "on", "pressed"), - border: border(layer, "on", "pressed"), - }, - hover: { - ...text(layer, "mono", "on", "hovered"), - background: background(layer, "on", "hovered"), - border: border(layer, "on", "hovered"), - }, - }, - button_margin: 8, - info_text_default: text(layer, "sans", "default", { size: "xs" }), - link_text_default: text(layer, "sans", "default", { size: "xs", underline: true }), - link_text_hover: text(layer, "sans", "hovered", { size: "xs", underline: true }) - }; + return { + submit_button: { + ...text(layer, "mono", "on"), + background: background(layer, "on"), + cornerRadius: 6, + border: border(layer, "on"), + margin: { + right: 4, + }, + padding: { + bottom: 2, + left: 10, + right: 10, + top: 2, + }, + clicked: { + ...text(layer, "mono", "on", "pressed"), + background: background(layer, "on", "pressed"), + border: border(layer, "on", "pressed"), + }, + hover: { + ...text(layer, "mono", "on", "hovered"), + background: background(layer, "on", "hovered"), + border: border(layer, "on", "hovered"), + }, + }, + button_margin: 8, + info_text_default: text(layer, "sans", "default", { size: "xs" }), + link_text_default: text(layer, "sans", "default", { size: "xs", underline: true }), + link_text_hover: text(layer, "sans", "hovered", { size: "xs", underline: true }) + } } diff --git a/styles/src/styleTree/hoverPopover.ts b/styles/src/styleTree/hoverPopover.ts index ab4beee11b2a0001b8656a5232b173918a987411..032c53112b27f9f6f47f378a23ef59a9ea40ac53 100644 --- a/styles/src/styleTree/hoverPopover.ts +++ b/styles/src/styleTree/hoverPopover.ts @@ -1,45 +1,45 @@ -import { ColorScheme } from "../themes/common/colorScheme"; -import { background, border, text } from "./components"; +import { ColorScheme } from "../themes/common/colorScheme" +import { background, border, text } from "./components" export default function HoverPopover(colorScheme: ColorScheme) { - let layer = colorScheme.middle; - let baseContainer = { - background: background(layer), - cornerRadius: 8, - padding: { - left: 8, - right: 8, - top: 4, - bottom: 4, - }, - shadow: colorScheme.popoverShadow, - border: border(layer), - margin: { - left: -8, - }, - }; + let layer = colorScheme.middle + let baseContainer = { + background: background(layer), + cornerRadius: 8, + padding: { + left: 8, + right: 8, + top: 4, + bottom: 4, + }, + shadow: colorScheme.popoverShadow, + border: border(layer), + margin: { + left: -8, + }, + } - return { - container: baseContainer, - infoContainer: { - ...baseContainer, - background: background(layer, "accent"), - border: border(layer, "accent"), - }, - warningContainer: { - ...baseContainer, - background: background(layer, "warning"), - border: border(layer, "warning"), - }, - errorContainer: { - ...baseContainer, - background: background(layer, "negative"), - border: border(layer, "negative"), - }, - block_style: { - padding: { top: 4 }, - }, - 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 - }; + return { + container: baseContainer, + infoContainer: { + ...baseContainer, + background: background(layer, "accent"), + border: border(layer, "accent"), + }, + warningContainer: { + ...baseContainer, + background: background(layer, "warning"), + border: border(layer, "warning"), + }, + errorContainer: { + ...baseContainer, + background: background(layer, "negative"), + border: border(layer, "negative"), + }, + block_style: { + padding: { top: 4 }, + }, + 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 + } } diff --git a/styles/src/styleTree/incomingCallNotification.ts b/styles/src/styleTree/incomingCallNotification.ts index 2540e5e78a16c52a373984f5f6a47f5077b17ae3..8a57554fce9562810de364a9c721f3694cbdd469 100644 --- a/styles/src/styleTree/incomingCallNotification.ts +++ b/styles/src/styleTree/incomingCallNotification.ts @@ -1,45 +1,53 @@ -import { ColorScheme } from "../themes/common/colorScheme"; -import { background, border, text } from "./components"; +import { ColorScheme } from "../themes/common/colorScheme" +import { background, border, text } from "./components" -export default function incomingCallNotification(colorScheme: ColorScheme): Object { - let layer = colorScheme.middle; - const avatarSize = 48; - return { - windowHeight: 74, - windowWidth: 380, - background: background(layer), - callerContainer: { - padding: 12, - }, - callerAvatar: { - height: avatarSize, - width: avatarSize, - cornerRadius: avatarSize / 2, - }, - callerMetadata: { - margin: { left: 10 }, - }, - callerUsername: { - ...text(layer, "sans", { size: "sm", weight: "bold" }), - margin: { top: -3 }, - }, - callerMessage: { - ...text(layer, "sans", "variant", { size: "xs" }), - margin: { top: -3 }, - }, - worktreeRoots: { - ...text(layer, "sans", "variant", { size: "xs", weight: "bold" }), - margin: { top: -3 }, - }, - buttonWidth: 96, - acceptButton: { - background: background(layer, "accent"), - border: border(layer, { left: true, bottom: true }), - ...text(layer, "sans", "positive", { size: "xs", weight: "extra_bold" }) - }, - declineButton: { - border: border(layer, { left: true }), - ...text(layer, "sans", "negative", { size: "xs", weight: "extra_bold" }) - }, - }; +export default function incomingCallNotification( + colorScheme: ColorScheme +): Object { + let layer = colorScheme.middle + const avatarSize = 48 + return { + windowHeight: 74, + windowWidth: 380, + background: background(layer), + callerContainer: { + padding: 12, + }, + callerAvatar: { + height: avatarSize, + width: avatarSize, + cornerRadius: avatarSize / 2, + }, + callerMetadata: { + margin: { left: 10 }, + }, + callerUsername: { + ...text(layer, "sans", { size: "sm", weight: "bold" }), + margin: { top: -3 }, + }, + callerMessage: { + ...text(layer, "sans", "variant", { size: "xs" }), + margin: { top: -3 }, + }, + worktreeRoots: { + ...text(layer, "sans", "variant", { size: "xs", weight: "bold" }), + margin: { top: -3 }, + }, + buttonWidth: 96, + acceptButton: { + background: background(layer, "accent"), + border: border(layer, { left: true, bottom: true }), + ...text(layer, "sans", "positive", { + size: "xs", + weight: "extra_bold", + }), + }, + declineButton: { + border: border(layer, { left: true }), + ...text(layer, "sans", "negative", { + size: "xs", + weight: "extra_bold", + }), + }, + } } diff --git a/styles/src/styleTree/picker.ts b/styles/src/styleTree/picker.ts index d124ea180e7580d58566f3d29e6735d78fcfb39d..2a6c3a0caf0bfe513ff4efef9c352a424a9f2843 100644 --- a/styles/src/styleTree/picker.ts +++ b/styles/src/styleTree/picker.ts @@ -1,78 +1,78 @@ -import { ColorScheme } from "../themes/common/colorScheme"; -import { background, border, text } from "./components"; +import { ColorScheme } from "../themes/common/colorScheme" +import { background, border, text } from "./components" export default function picker(colorScheme: ColorScheme) { - let layer = colorScheme.lowest; - const container = { - background: background(layer), - border: border(layer), - shadow: colorScheme.modalShadow, - cornerRadius: 12, - padding: { - bottom: 4, + let layer = colorScheme.lowest + const container = { + background: background(layer), + border: border(layer), + shadow: colorScheme.modalShadow, + cornerRadius: 12, + padding: { + bottom: 4, + }, } - }; - const inputEditor = { - placeholderText: text(layer, "sans", "on", "disabled"), - selection: colorScheme.players[0], - text: text(layer, "mono", "on"), - border: border(layer, { bottom: true }), - padding: { - bottom: 8, - left: 16, - right: 16, - top: 8, - }, - margin: { - bottom: 4, - }, - }; - const emptyInputEditor = { ...inputEditor }; - delete emptyInputEditor.border; - delete emptyInputEditor.margin; + const inputEditor = { + placeholderText: text(layer, "sans", "on", "disabled"), + selection: colorScheme.players[0], + text: text(layer, "mono", "on"), + border: border(layer, { bottom: true }), + padding: { + bottom: 8, + left: 16, + right: 16, + top: 8, + }, + margin: { + bottom: 4, + }, + } + const emptyInputEditor = { ...inputEditor } + delete emptyInputEditor.border + delete emptyInputEditor.margin - return { - ...container, - emptyContainer: { - ...container, - padding: {} - }, - item: { - padding: { - bottom: 4, - left: 12, - right: 12, - top: 4, - }, - margin: { - top: 1, - left: 4, - right: 4, - }, - cornerRadius: 8, - text: text(layer, "sans", "variant"), - highlightText: text(layer, "sans", "accent", { weight: "bold" }), - active: { - background: background(layer, "base", "active"), - text: text(layer, "sans", "base", "active"), - highlightText: text(layer, "sans", "accent", { - weight: "bold", - }), - }, - hover: { - background: background(layer, "hovered"), - }, - }, - inputEditor, - emptyInputEditor, - noMatches: { - text: text(layer, "sans", "variant"), - padding: { - bottom: 8, - left: 16, - right: 16, - top: 8, - }, - }, - }; + return { + ...container, + emptyContainer: { + ...container, + padding: {}, + }, + item: { + padding: { + bottom: 4, + left: 12, + right: 12, + top: 4, + }, + margin: { + top: 1, + left: 4, + right: 4, + }, + cornerRadius: 8, + text: text(layer, "sans", "variant"), + highlightText: text(layer, "sans", "accent", { weight: "bold" }), + active: { + background: background(layer, "base", "active"), + text: text(layer, "sans", "base", "active"), + highlightText: text(layer, "sans", "accent", { + weight: "bold", + }), + }, + hover: { + background: background(layer, "hovered"), + }, + }, + inputEditor, + emptyInputEditor, + noMatches: { + text: text(layer, "sans", "variant"), + padding: { + bottom: 8, + left: 16, + right: 16, + top: 8, + }, + }, + } } diff --git a/styles/src/styleTree/projectDiagnostics.ts b/styles/src/styleTree/projectDiagnostics.ts index c80a5e1052a4973bc37a1cf7771038de70423e1e..127cbdb353d3d8c2ff921abae262605bda103bea 100644 --- a/styles/src/styleTree/projectDiagnostics.ts +++ b/styles/src/styleTree/projectDiagnostics.ts @@ -1,13 +1,13 @@ -import { ColorScheme } from "../themes/common/colorScheme"; -import { background, text } from "./components"; +import { ColorScheme } from "../themes/common/colorScheme" +import { background, text } from "./components" export default function projectDiagnostics(colorScheme: ColorScheme) { - let layer = colorScheme.highest; - return { - background: background(layer), - tabIconSpacing: 4, - tabIconWidth: 13, - tabSummarySpacing: 10, - emptyMessage: text(layer, "sans", "variant", { size: "md" }), - }; + let layer = colorScheme.highest + return { + background: background(layer), + tabIconSpacing: 4, + tabIconWidth: 13, + tabSummarySpacing: 10, + emptyMessage: text(layer, "sans", "variant", { size: "md" }), + } } diff --git a/styles/src/styleTree/projectPanel.ts b/styles/src/styleTree/projectPanel.ts index 357070e674043005d7fcfd32915bb24a9039823e..90e0c82f5bf1d16e19acfa0c3df0a0454cabae86 100644 --- a/styles/src/styleTree/projectPanel.ts +++ b/styles/src/styleTree/projectPanel.ts @@ -1,60 +1,60 @@ -import { ColorScheme } from "../themes/common/colorScheme"; -import { withOpacity } from "../utils/color"; -import { background, border, foreground, text } from "./components"; +import { ColorScheme } from "../themes/common/colorScheme" +import { withOpacity } from "../utils/color" +import { background, border, foreground, text } from "./components" export default function projectPanel(colorScheme: ColorScheme) { - let layer = colorScheme.middle; - - let baseEntry = { - height: 24, - iconColor: foreground(layer, "variant"), - iconSize: 8, - iconSpacing: 8, - } + let layer = colorScheme.middle - let entry = { - ...baseEntry, - text: text(layer, "mono", "variant", { size: "sm" }), - hover: { - background: background(layer, "variant", "hovered"), - }, - active: { - background: background(layer, "active"), - text: text(layer, "mono", "active", { size: "sm" }), - }, - activeHover: { - background: background(layer, "active"), - text: text(layer, "mono", "active", { size: "sm" }), - }, - }; + let baseEntry = { + height: 24, + iconColor: foreground(layer, "variant"), + iconSize: 8, + iconSpacing: 8, + } - return { - background: background(layer), - padding: { left: 12, right: 12, top: 6, bottom: 6 }, - indentWidth: 8, - entry, - draggedEntry: { - ...baseEntry, - text: text(layer, "mono", "on", { size: "sm" }), - background: withOpacity(background(layer, "on"), 0.9), - border: border(layer), - }, - ignoredEntry: { - ...entry, - text: text(layer, "mono", "disabled"), - }, - cutEntry: { - ...entry, - text: text(layer, "mono", "disabled"), - active: { - background: background(layer, "active"), - text: text(layer, "mono", "disabled", { size: "sm" }), - }, - }, - filenameEditor: { - background: background(layer, "on"), - text: text(layer, "mono", "on", { size: "sm" }), - selection: colorScheme.players[0], - }, - }; + let entry = { + ...baseEntry, + text: text(layer, "mono", "variant", { size: "sm" }), + hover: { + background: background(layer, "variant", "hovered"), + }, + active: { + background: background(layer, "active"), + text: text(layer, "mono", "active", { size: "sm" }), + }, + activeHover: { + background: background(layer, "active"), + text: text(layer, "mono", "active", { size: "sm" }), + }, + } + + return { + background: background(layer), + padding: { left: 12, right: 12, top: 6, bottom: 6 }, + indentWidth: 8, + entry, + draggedEntry: { + ...baseEntry, + text: text(layer, "mono", "on", { size: "sm" }), + background: withOpacity(background(layer, "on"), 0.9), + border: border(layer), + }, + ignoredEntry: { + ...entry, + text: text(layer, "mono", "disabled"), + }, + cutEntry: { + ...entry, + text: text(layer, "mono", "disabled"), + active: { + background: background(layer, "active"), + text: text(layer, "mono", "disabled", { size: "sm" }), + }, + }, + filenameEditor: { + background: background(layer, "on"), + text: text(layer, "mono", "on", { size: "sm" }), + selection: colorScheme.players[0], + }, + } } diff --git a/styles/src/styleTree/projectSharedNotification.ts b/styles/src/styleTree/projectSharedNotification.ts index 05542ca20c3aded4be4f01ec20d76a830289763e..af731f8850ca51d5f9d2a48a9f78579fe3f602d1 100644 --- a/styles/src/styleTree/projectSharedNotification.ts +++ b/styles/src/styleTree/projectSharedNotification.ts @@ -1,46 +1,54 @@ -import { ColorScheme } from "../themes/common/colorScheme"; -import { background, border, 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( + colorScheme: ColorScheme +): Object { + let layer = colorScheme.middle - const avatarSize = 48; - return { - windowHeight: 74, - windowWidth: 380, - background: background(layer), - ownerContainer: { - padding: 12, - }, - ownerAvatar: { - height: avatarSize, - width: avatarSize, - cornerRadius: avatarSize / 2, - }, - ownerMetadata: { - margin: { left: 10 }, - }, - ownerUsername: { - ...text(layer, "sans", { size: "sm", weight: "bold" }), - margin: { top: -3 }, - }, - message: { - ...text(layer, "sans", "variant", { size: "xs" }), - margin: { top: -3 }, - }, - worktreeRoots: { - ...text(layer, "sans", "variant", { size: "xs", weight: "bold" }), - margin: { top: -3 }, - }, - buttonWidth: 96, - openButton: { - background: background(layer, "accent"), - border: border(layer, { left: true, bottom: true, }), - ...text(layer, "sans", "accent", { size: "xs", weight: "extra_bold" }) - }, - dismissButton: { - border: border(layer, { left: true }), - ...text(layer, "sans", "variant", { size: "xs", weight: "extra_bold" }) - }, - }; + const avatarSize = 48 + return { + windowHeight: 74, + windowWidth: 380, + background: background(layer), + ownerContainer: { + padding: 12, + }, + ownerAvatar: { + height: avatarSize, + width: avatarSize, + cornerRadius: avatarSize / 2, + }, + ownerMetadata: { + margin: { left: 10 }, + }, + ownerUsername: { + ...text(layer, "sans", { size: "sm", weight: "bold" }), + margin: { top: -3 }, + }, + message: { + ...text(layer, "sans", "variant", { size: "xs" }), + margin: { top: -3 }, + }, + worktreeRoots: { + ...text(layer, "sans", "variant", { size: "xs", weight: "bold" }), + margin: { top: -3 }, + }, + buttonWidth: 96, + openButton: { + background: background(layer, "accent"), + border: border(layer, { left: true, bottom: true }), + ...text(layer, "sans", "accent", { + size: "xs", + weight: "extra_bold", + }), + }, + dismissButton: { + border: border(layer, { left: true }), + ...text(layer, "sans", "variant", { + size: "xs", + weight: "extra_bold", + }), + }, + } } diff --git a/styles/src/styleTree/search.ts b/styles/src/styleTree/search.ts index 2edd3807a53928bf01b796e0e9d306f27c03a420..7a00b932784909fe8bfc8ba20772db7059adece9 100644 --- a/styles/src/styleTree/search.ts +++ b/styles/src/styleTree/search.ts @@ -1,96 +1,94 @@ -import { ColorScheme } from "../themes/common/colorScheme"; -import { withOpacity } from "../utils/color"; -import { background, border, foreground, text } from "./components"; +import { ColorScheme } from "../themes/common/colorScheme" +import { withOpacity } from "../utils/color" +import { background, border, foreground, text } from "./components" export default function search(colorScheme: ColorScheme) { - let layer = colorScheme.highest; + let layer = colorScheme.highest - // Search input - const editor = { - background: background(layer), - cornerRadius: 8, - minWidth: 200, - maxWidth: 500, - placeholderText: text(layer, "mono", "disabled"), - selection: colorScheme.players[0], - text: text(layer, "mono", "default"), - border: border(layer), - margin: { - right: 12, - }, - padding: { - top: 3, - bottom: 3, - left: 12, - right: 8, - }, - }; + // Search input + const editor = { + background: background(layer), + cornerRadius: 8, + minWidth: 200, + maxWidth: 500, + placeholderText: text(layer, "mono", "disabled"), + selection: colorScheme.players[0], + text: text(layer, "mono", "default"), + border: border(layer), + margin: { + right: 12, + }, + padding: { + top: 3, + bottom: 3, + left: 12, + right: 8, + }, + } - return { - // TODO: Add an activeMatchBackground on the rust side to differenciate between active and inactive - matchBackground: withOpacity(foreground(layer, "accent"), 0.4), - tabIconSpacing: 8, - tabIconWidth: 14, - optionButton: { - ...text(layer, "mono", "on"), - background: background(layer, "on"), - cornerRadius: 6, - border: border(layer, "on"), - margin: { - right: 4, - }, - padding: { - bottom: 2, - left: 10, - right: 10, - top: 2, - }, - active: { - ...text(layer, "mono", "on", "inverted"), - background: background(layer, "on", "inverted"), - border: border(layer, "on", "inverted"), - }, - clicked: { - ...text(layer, "mono", "on", "pressed"), - background: background(layer, "on", "pressed"), - border: border(layer, "on", "pressed"), - }, - hover: { - ...text(layer, "mono", "on", "hovered"), - background: background(layer, "on", "hovered"), - border: border(layer, "on", "hovered"), - }, - }, - editor, - invalidEditor: { - ...editor, - border: border(layer, "negative"), - }, - matchIndex: { - ...text(layer, "mono", "variant"), - padding: 6, - }, - optionButtonGroup: { - padding: { - left: 12, - right: 12, - }, - }, - resultsStatus: { - ...text(layer, "mono", "on"), - size: 18, - }, - dismissButton: { - color: foreground(layer, "variant"), - iconWidth: 12, - buttonWidth: 14, - padding: { - left: 10, - right: 10, - }, - hover: { - color: foreground(layer, "hovered"), - }, - }, - }; + return { + // TODO: Add an activeMatchBackground on the rust side to differenciate between active and inactive + matchBackground: withOpacity(foreground(layer, "accent"), 0.4), + optionButton: { + ...text(layer, "mono", "on"), + background: background(layer, "on"), + cornerRadius: 6, + border: border(layer, "on"), + margin: { + right: 4, + }, + padding: { + bottom: 2, + left: 10, + right: 10, + top: 2, + }, + active: { + ...text(layer, "mono", "on", "inverted"), + background: background(layer, "on", "inverted"), + border: border(layer, "on", "inverted"), + }, + clicked: { + ...text(layer, "mono", "on", "pressed"), + background: background(layer, "on", "pressed"), + border: border(layer, "on", "pressed"), + }, + hover: { + ...text(layer, "mono", "on", "hovered"), + background: background(layer, "on", "hovered"), + border: border(layer, "on", "hovered"), + }, + }, + editor, + invalidEditor: { + ...editor, + border: border(layer, "negative"), + }, + matchIndex: { + ...text(layer, "mono", "variant"), + padding: 6, + }, + optionButtonGroup: { + padding: { + left: 12, + right: 12, + }, + }, + resultsStatus: { + ...text(layer, "mono", "on"), + size: 18, + }, + dismissButton: { + color: foreground(layer, "variant"), + iconWidth: 12, + buttonWidth: 14, + padding: { + left: 10, + right: 10, + }, + hover: { + color: foreground(layer, "hovered"), + }, + }, + } } diff --git a/styles/src/styleTree/sharedScreen.ts b/styles/src/styleTree/sharedScreen.ts index 8444df7418aed2f374591051bf09ac15e54d2f0c..cd0d44236ec97d50da1ae5c96463236ff401f34c 100644 --- a/styles/src/styleTree/sharedScreen.ts +++ b/styles/src/styleTree/sharedScreen.ts @@ -1,9 +1,9 @@ -import { ColorScheme } from "../themes/common/colorScheme"; -import { background } from "./components"; +import { ColorScheme } from "../themes/common/colorScheme" +import { background } from "./components" export default function sharedScreen(colorScheme: ColorScheme) { - let layer = colorScheme.highest; - return { - background: background(layer) - } + let layer = colorScheme.highest + return { + background: background(layer), + } } diff --git a/styles/src/styleTree/simpleMessageNotification.ts b/styles/src/styleTree/simpleMessageNotification.ts index 2697bedc777e21d7fda8fdfef59a697de24bfd6c..36b295c640241ce161fecff3450a85421aca75e3 100644 --- a/styles/src/styleTree/simpleMessageNotification.ts +++ b/styles/src/styleTree/simpleMessageNotification.ts @@ -1,31 +1,33 @@ -import { ColorScheme } from "../themes/common/colorScheme"; -import { foreground, text } from "./components"; +import { ColorScheme } from "../themes/common/colorScheme" +import { foreground, text } from "./components" -const headerPadding = 8; +const headerPadding = 8 -export default function simpleMessageNotification(colorScheme: ColorScheme): Object { - let layer = colorScheme.middle; - return { - message: { - ...text(layer, "sans", { size: "xs" }), - margin: { left: headerPadding, right: headerPadding }, - }, - actionMessage: { - ...text(layer, "sans", { size: "xs" }), - margin: { left: headerPadding, top: 6, bottom: 6 }, - hover: { - color: foreground(layer, "hovered"), - }, - }, - dismissButton: { - color: foreground(layer), - iconWidth: 8, - iconHeight: 8, - buttonWidth: 8, - buttonHeight: 8, - hover: { - color: foreground(layer, "hovered"), - }, - }, - }; +export default function simpleMessageNotification( + colorScheme: ColorScheme +): Object { + let layer = colorScheme.middle + return { + message: { + ...text(layer, "sans", { size: "xs" }), + margin: { left: headerPadding, right: headerPadding }, + }, + actionMessage: { + ...text(layer, "sans", { size: "xs" }), + margin: { left: headerPadding, top: 6, bottom: 6 }, + hover: { + color: foreground(layer, "hovered"), + }, + }, + dismissButton: { + color: foreground(layer), + iconWidth: 8, + iconHeight: 8, + buttonWidth: 8, + buttonHeight: 8, + hover: { + color: foreground(layer, "hovered"), + }, + }, + } } diff --git a/styles/src/styleTree/statusBar.ts b/styles/src/styleTree/statusBar.ts index bd9a9e2f00a95b179c7f93d2c42005afabecb9b5..a60a55df1ecaae3ce52edfc82c90222db30718cb 100644 --- a/styles/src/styleTree/statusBar.ts +++ b/styles/src/styleTree/statusBar.ts @@ -1,118 +1,118 @@ -import { ColorScheme } from "../themes/common/colorScheme"; -import { background, border, foreground, text } from "./components"; +import { ColorScheme } from "../themes/common/colorScheme" +import { background, border, foreground, text } from "./components" export default function statusBar(colorScheme: ColorScheme) { - let layer = colorScheme.lowest; + let layer = colorScheme.lowest - const statusContainer = { - cornerRadius: 6, - padding: { top: 3, bottom: 3, left: 6, right: 6 }, - }; - - const diagnosticStatusContainer = { - cornerRadius: 6, - padding: { top: 1, bottom: 1, left: 6, right: 6 }, - }; + const statusContainer = { + cornerRadius: 6, + padding: { top: 3, bottom: 3, left: 6, right: 6 }, + } - return { - height: 30, - itemSpacing: 8, - padding: { - top: 1, - bottom: 1, - left: 6, - right: 6, - }, - 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(layer, "sans"), - iconColor: foreground(layer), - hover: { - message: text(layer, "sans"), - iconColor: foreground(layer), - background: background(layer), - }, - }, - diagnosticMessage: { - ...text(layer, "sans"), - hover: text(layer, "sans", "hovered"), - }, - feedback: { - ...text(layer, "sans", "variant"), - hover: text(layer, "sans", "hovered"), - }, - diagnosticSummary: { - height: 20, - iconWidth: 16, - iconSpacing: 2, - summarySpacing: 6, - text: text(layer, "sans", { size: "sm" }), - iconColorOk: foreground(layer, "variant"), - iconColorWarning: foreground(layer, "warning"), - iconColorError: foreground(layer, "negative"), - containerOk: { + const diagnosticStatusContainer = { cornerRadius: 6, - padding: { top: 3, bottom: 3, left: 7, right: 7 }, - }, - containerWarning: { - ...diagnosticStatusContainer, - background: background(layer, "warning"), - border: border(layer, "warning"), - }, - containerError: { - ...diagnosticStatusContainer, - background: background(layer, "negative"), - border: border(layer, "negative"), - }, - hover: { - iconColorOk: foreground(layer, "on"), - containerOk: { - cornerRadius: 6, - padding: { top: 3, bottom: 3, left: 7, right: 7 }, - background: background(layer, "on", "hovered"), + padding: { top: 1, bottom: 1, left: 6, right: 6 }, + } + + return { + height: 30, + itemSpacing: 8, + padding: { + top: 1, + bottom: 1, + left: 6, + right: 6, + }, + 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(layer, "sans"), + iconColor: foreground(layer), + hover: { + message: text(layer, "sans"), + iconColor: foreground(layer), + background: background(layer), + }, }, - containerWarning: { - ...diagnosticStatusContainer, - background: background(layer, "warning", "hovered"), - border: border(layer, "warning", "hovered"), + diagnosticMessage: { + ...text(layer, "sans"), + hover: text(layer, "sans", "hovered"), }, - containerError: { - ...diagnosticStatusContainer, - background: background(layer, "negative", "hovered"), - border: border(layer, "negative", "hovered"), + feedback: { + ...text(layer, "sans", "variant"), + hover: text(layer, "sans", "hovered"), }, - }, - }, - sidebarButtons: { - groupLeft: {}, - groupRight: {}, - item: { - ...statusContainer, - iconSize: 16, - iconColor: foreground(layer, "variant"), - hover: { - iconColor: foreground(layer, "hovered"), - background: background(layer, "variant"), + diagnosticSummary: { + height: 20, + iconWidth: 16, + iconSpacing: 2, + summarySpacing: 6, + 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: background(layer, "warning"), + border: border(layer, "warning"), + }, + containerError: { + ...diagnosticStatusContainer, + background: background(layer, "negative"), + border: border(layer, "negative"), + }, + hover: { + iconColorOk: foreground(layer, "on"), + containerOk: { + cornerRadius: 6, + padding: { top: 3, bottom: 3, left: 7, right: 7 }, + background: background(layer, "on", "hovered"), + }, + containerWarning: { + ...diagnosticStatusContainer, + background: background(layer, "warning", "hovered"), + border: border(layer, "warning", "hovered"), + }, + containerError: { + ...diagnosticStatusContainer, + background: background(layer, "negative", "hovered"), + border: border(layer, "negative", "hovered"), + }, + }, }, - active: { - iconColor: foreground(layer, "active"), - background: background(layer, "active"), + sidebarButtons: { + groupLeft: {}, + groupRight: {}, + item: { + ...statusContainer, + iconSize: 16, + iconColor: foreground(layer, "variant"), + hover: { + iconColor: foreground(layer, "hovered"), + background: background(layer, "variant"), + }, + active: { + iconColor: foreground(layer, "active"), + background: background(layer, "active"), + }, + }, + badge: { + cornerRadius: 3, + padding: 2, + margin: { bottom: -1, right: -1 }, + border: border(layer), + background: background(layer, "accent"), + }, }, - }, - badge: { - cornerRadius: 3, - padding: 2, - margin: { bottom: -1, right: -1 }, - border: border(layer), - background: background(layer, "accent"), - }, - }, - }; + } } diff --git a/styles/src/styleTree/tabBar.ts b/styles/src/styleTree/tabBar.ts index a4875cec06a57bafde5455bff1d9d2bd9322f4f0..c06195f4816039bae12f66c3b06fe3bb78f35f5c 100644 --- a/styles/src/styleTree/tabBar.ts +++ b/styles/src/styleTree/tabBar.ts @@ -1,103 +1,103 @@ -import { ColorScheme } from "../themes/common/colorScheme"; -import { withOpacity } from "../utils/color"; -import { text, border, background, foreground } from "./components"; +import { ColorScheme } from "../themes/common/colorScheme" +import { withOpacity } from "../utils/color" +import { text, border, background, foreground } from "./components" export default function tabBar(colorScheme: ColorScheme) { - const height = 32; + const height = 32 - let activeLayer = colorScheme.highest; - let layer = colorScheme.middle; + let activeLayer = colorScheme.highest + let layer = colorScheme.middle - const tab = { - height, - text: text(layer, "sans", "variant", { size: "sm" }), - background: background(layer), - border: border(layer, { - right: true, - bottom: true, - overlay: true, - }), - padding: { - left: 8, - right: 12, - }, - spacing: 8, + const tab = { + height, + text: text(layer, "sans", "variant", { size: "sm" }), + background: background(layer), + border: border(layer, { + right: true, + bottom: true, + overlay: true, + }), + padding: { + left: 8, + right: 12, + }, + spacing: 8, - // Close icons - iconWidth: 8, - iconClose: foreground(layer, "variant"), - iconCloseActive: foreground(layer, "hovered"), + // Close icons + iconWidth: 14, + iconClose: foreground(layer, "variant"), + iconCloseActive: foreground(layer, "hovered"), - // Indicators - iconConflict: foreground(layer, "warning"), - iconDirty: foreground(layer, "accent"), + // 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: 8 }, - ...text(layer, "sans", "disabled", { size: "2xs" }), - }, - }; + // When two tabs of the same name are open, a label appears next to them + description: { + margin: { left: 8 }, + ...text(layer, "sans", "disabled", { size: "2xs" }), + }, + } - const activePaneActiveTab = { - ...tab, - background: background(activeLayer), - text: text(activeLayer, "sans", "active", { size: "sm" }), - border: { - ...tab.border, - bottom: false, - }, - }; + const activePaneActiveTab = { + ...tab, + background: background(activeLayer), + text: text(activeLayer, "sans", "active", { size: "sm" }), + border: { + ...tab.border, + bottom: false, + }, + } - const inactivePaneInactiveTab = { - ...tab, - background: background(layer), - text: text(layer, "sans", "variant", { size: "sm" }), - }; + const inactivePaneInactiveTab = { + ...tab, + background: background(layer), + text: text(layer, "sans", "variant", { size: "sm" }), + } - const inactivePaneActiveTab = { - ...tab, - background: background(activeLayer), - text: text(layer, "sans", "variant", { size: "sm" }), - border: { - ...tab.border, - bottom: false, - }, - }; + const inactivePaneActiveTab = { + ...tab, + background: background(activeLayer), + text: text(layer, "sans", "variant", { size: "sm" }), + border: { + ...tab.border, + bottom: false, + }, + } - const draggedTab = { - ...activePaneActiveTab, - background: withOpacity(tab.background, 0.9), - border: undefined as any, - shadow: colorScheme.popoverShadow, - }; + const draggedTab = { + ...activePaneActiveTab, + background: withOpacity(tab.background, 0.9), + border: undefined as any, + shadow: colorScheme.popoverShadow, + } - return { - height, - background: background(layer), - activePane: { - activeTab: activePaneActiveTab, - inactiveTab: tab, - }, - inactivePane: { - activeTab: inactivePaneActiveTab, - inactiveTab: inactivePaneInactiveTab, - }, - draggedTab, - paneButton: { - color: foreground(layer, "variant"), - iconWidth: 12, - buttonWidth: activePaneActiveTab.height, - hover: { - color: foreground(layer, "hovered"), - }, - }, - paneButtonContainer: { - background: tab.background, - border: { - ...tab.border, - right: false, - }, - }, - }; + return { + height, + background: background(layer), + activePane: { + activeTab: activePaneActiveTab, + inactiveTab: tab, + }, + inactivePane: { + activeTab: inactivePaneActiveTab, + inactiveTab: inactivePaneInactiveTab, + }, + draggedTab, + paneButton: { + color: foreground(layer, "variant"), + iconWidth: 12, + buttonWidth: activePaneActiveTab.height, + hover: { + color: foreground(layer, "hovered"), + }, + }, + paneButtonContainer: { + background: tab.background, + border: { + ...tab.border, + right: false, + }, + }, + } } diff --git a/styles/src/styleTree/terminal.ts b/styles/src/styleTree/terminal.ts index 2f5c34e5b8d124c0a6dbdc64541b1b3ce55bccaa..4158ace300de0dc9fb0c3c3f3707949c5b610e2e 100644 --- a/styles/src/styleTree/terminal.ts +++ b/styles/src/styleTree/terminal.ts @@ -1,52 +1,52 @@ -import { ColorScheme } from "../themes/common/colorScheme"; +import { ColorScheme } from "../themes/common/colorScheme" 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 - */ - 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 + * 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 */ - foreground: colorScheme.ramps.neutral(1).hex(), - /** - * Default color for the rectangle background of a cell - */ - background: colorScheme.ramps.neutral(0).hex(), - modalBackground: colorScheme.ramps.neutral(0.1).hex(), - /** - * Default color for the cursor - */ - 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(), - }; + 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: colorScheme.ramps.neutral(1).hex(), + /** + * Default color for the rectangle background of a cell + */ + background: colorScheme.ramps.neutral(0).hex(), + modalBackground: colorScheme.ramps.neutral(0.1).hex(), + /** + * Default color for the cursor + */ + 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(), + } } diff --git a/styles/src/styleTree/tooltip.ts b/styles/src/styleTree/tooltip.ts index 5a43b1ef4c6c5ce277f1ad275f932eda5154c750..bb66cd413cddb2556b337ade3fe9a238cc59088b 100644 --- a/styles/src/styleTree/tooltip.ts +++ b/styles/src/styleTree/tooltip.ts @@ -1,23 +1,23 @@ -import { ColorScheme } from "../themes/common/colorScheme"; -import { background, border, text } from "./components"; +import { ColorScheme } from "../themes/common/colorScheme" +import { background, border, text } from "./components" export default function tooltip(colorScheme: ColorScheme) { - let layer = colorScheme.middle; - return { - background: background(layer), - border: border(layer), - padding: { top: 4, bottom: 4, left: 8, right: 8 }, - margin: { top: 6, left: 6 }, - shadow: colorScheme.popoverShadow, - cornerRadius: 6, - text: text(layer, "sans", { size: "xs" }), - keystroke: { - background: background(layer, "on"), - cornerRadius: 4, - margin: { left: 6 }, - padding: { left: 4, right: 4 }, - ...text(layer, "mono", "on", { size: "xs", weight: "bold" }), - }, - maxTextWidth: 200, - }; + let layer = colorScheme.middle + return { + background: background(layer), + border: border(layer), + padding: { top: 4, bottom: 4, left: 8, right: 8 }, + margin: { top: 6, left: 6 }, + shadow: colorScheme.popoverShadow, + cornerRadius: 6, + text: text(layer, "sans", { size: "xs" }), + keystroke: { + background: background(layer, "on"), + cornerRadius: 4, + margin: { left: 6 }, + padding: { left: 4, right: 4 }, + ...text(layer, "mono", "on", { size: "xs", weight: "bold" }), + }, + maxTextWidth: 200, + } } diff --git a/styles/src/styleTree/updateNotification.ts b/styles/src/styleTree/updateNotification.ts index 08c0b0843074160c962c0cb0e623c1f486603c76..44472a766265bb81ab9c6f9d5003dfd58ea8675b 100644 --- a/styles/src/styleTree/updateNotification.ts +++ b/styles/src/styleTree/updateNotification.ts @@ -1,31 +1,31 @@ -import { ColorScheme } from "../themes/common/colorScheme"; -import { foreground, text } from "./components"; +import { ColorScheme } from "../themes/common/colorScheme" +import { foreground, text } from "./components" -const headerPadding = 8; +const headerPadding = 8 export default function updateNotification(colorScheme: ColorScheme): Object { - let layer = colorScheme.middle; - return { - message: { - ...text(layer, "sans", { size: "xs" }), - margin: { left: headerPadding, right: headerPadding }, - }, - actionMessage: { - ...text(layer, "sans", { size: "xs" }), - margin: { left: headerPadding, top: 6, bottom: 6 }, - hover: { - color: foreground(layer, "hovered"), - }, - }, - dismissButton: { - color: foreground(layer), - iconWidth: 8, - iconHeight: 8, - buttonWidth: 8, - buttonHeight: 8, - hover: { - color: foreground(layer, "hovered"), - }, - }, - }; + let layer = colorScheme.middle + return { + message: { + ...text(layer, "sans", { size: "xs" }), + margin: { left: headerPadding, right: headerPadding }, + }, + actionMessage: { + ...text(layer, "sans", { size: "xs" }), + margin: { left: headerPadding, top: 6, bottom: 6 }, + hover: { + color: foreground(layer, "hovered"), + }, + }, + dismissButton: { + color: foreground(layer), + iconWidth: 8, + iconHeight: 8, + buttonWidth: 8, + buttonHeight: 8, + hover: { + color: foreground(layer, "hovered"), + }, + }, + } } diff --git a/styles/src/styleTree/workspace.ts b/styles/src/styleTree/workspace.ts index 769ea85a00276cc410b3165b326e4360e59f59f2..f9f49e3c7df14b5ccf14a9322a1a461c1c153917 100644 --- a/styles/src/styleTree/workspace.ts +++ b/styles/src/styleTree/workspace.ts @@ -1,235 +1,264 @@ -import { ColorScheme } from "../themes/common/colorScheme"; -import { withOpacity } from "../utils/color"; -import { - background, - border, - borderColor, - foreground, - text, -} from "./components"; -import statusBar from "./statusBar"; -import tabBar from "./tabBar"; +import { ColorScheme } from "../themes/common/colorScheme" +import { withOpacity } from "../utils/color" +import { background, border, borderColor, foreground, text } from "./components" +import statusBar from "./statusBar" +import tabBar from "./tabBar" export default function workspace(colorScheme: ColorScheme) { - const layer = colorScheme.lowest; - const titlebarPadding = 6; - const titlebarButton = { - cornerRadius: 6, - padding: { - top: 1, - bottom: 1, - left: 8, - right: 8, - }, - ...text(layer, "sans", "variant", { size: "xs" }), - background: background(layer, "variant"), - border: border(layer), - hover: { - ...text(layer, "sans", "variant", "hovered", { size: "xs" }), - background: background(layer, "variant", "hovered"), - border: border(layer, "variant", "hovered"), - }, - }; - const avatarWidth = 18; + const layer = colorScheme.lowest + const itemSpacing = 8 + const titlebarButton = { + cornerRadius: 6, + padding: { + top: 1, + bottom: 1, + left: 8, + right: 8, + }, + ...text(layer, "sans", "variant", { size: "xs" }), + background: background(layer, "variant"), + border: border(layer), + hover: { + ...text(layer, "sans", "variant", "hovered", { size: "xs" }), + background: background(layer, "variant", "hovered"), + border: border(layer, "variant", "hovered"), + }, + clicked: { + ...text(layer, "sans", "variant", "pressed", { size: "xs" }), + background: background(layer, "variant", "pressed"), + border: border(layer, "variant", "pressed"), + }, + active: { + ...text(layer, "sans", "variant", "active", { size: "xs" }), + background: background(layer, "variant", "active"), + border: border(layer, "variant", "active"), + }, + } + const avatarWidth = 18 + const avatarOuterWidth = avatarWidth + 4 + const followerAvatarWidth = 14 + const followerAvatarOuterWidth = followerAvatarWidth + 4 - return { - background: background(layer), - joiningProjectAvatar: { - cornerRadius: 40, - width: 80, - }, - joiningProjectMessage: { - padding: 12, - ...text(layer, "sans", { size: "lg" }), - }, - externalLocationMessage: { - background: background(colorScheme.middle, "accent"), - border: border(colorScheme.middle, "accent"), - cornerRadius: 6, - padding: 12, - margin: { bottom: 8, right: 8 }, - ...text(colorScheme.middle, "sans", "accent", { size: "xs" }), - }, - leaderBorderOpacity: 0.7, - leaderBorderWidth: 2.0, - tabBar: tabBar(colorScheme), - modal: { - margin: { - bottom: 52, - top: 52, - }, - cursor: "Arrow", - }, - sidebar: { - initialSize: 240, - border: border(layer, { left: true, right: true }), - }, - paneDivider: { - color: borderColor(layer), - width: 1, - }, - statusBar: statusBar(colorScheme), - titlebar: { - avatarWidth, - avatarMargin: 8, - height: 33, // 32px + 1px for overlaid border - background: background(layer), - border: border(layer, { bottom: true, overlay: true }), - padding: { - left: 80, - right: titlebarPadding, - }, + return { + background: background(layer), + joiningProjectAvatar: { + cornerRadius: 40, + width: 80, + }, + joiningProjectMessage: { + padding: 12, + ...text(layer, "sans", { size: "lg" }), + }, + externalLocationMessage: { + background: background(colorScheme.middle, "accent"), + border: border(colorScheme.middle, "accent"), + cornerRadius: 6, + padding: 12, + margin: { bottom: 8, right: 8 }, + ...text(colorScheme.middle, "sans", "accent", { size: "xs" }), + }, + leaderBorderOpacity: 0.7, + leaderBorderWidth: 2.0, + tabBar: tabBar(colorScheme), + modal: { + margin: { + bottom: 52, + top: 52, + }, + cursor: "Arrow", + }, + sidebar: { + initialSize: 240, + border: border(layer, { left: true, right: true }), + }, + paneDivider: { + color: borderColor(layer), + width: 1, + }, + statusBar: statusBar(colorScheme), + titlebar: { + itemSpacing, + facePileSpacing: 2, + height: 33, // 32px + 1px for overlaid border + background: background(layer), + border: border(layer, { bottom: true, overlay: true }), + padding: { + left: 80, + right: itemSpacing, + }, - // Project - title: text(layer, "sans", "variant"), + // Project + title: text(layer, "sans", "variant"), - // Collaborators - avatar: { - cornerRadius: avatarWidth / 2, - border: { - color: "#00000088", - width: 1, - }, - }, - inactiveAvatar: { - cornerRadius: avatarWidth / 2, - border: { - color: "#00000088", - width: 1, - }, - grayscale: true, - }, - avatarRibbon: { - height: 3, - width: 12, - // TODO: Chore: Make avatarRibbon colors driven by the theme rather than being hard coded. - }, + // Collaborators + leaderAvatar: { + width: avatarWidth, + outerWidth: avatarOuterWidth, + cornerRadius: avatarWidth / 2, + outerCornerRadius: avatarOuterWidth / 2, + }, + followerAvatar: { + width: followerAvatarWidth, + outerWidth: followerAvatarOuterWidth, + cornerRadius: followerAvatarWidth / 2, + outerCornerRadius: followerAvatarOuterWidth / 2, + }, + inactiveAvatarGrayscale: true, + followerAvatarOverlap: 8, + leaderSelection: { + margin: { + top: 4, + bottom: 4, + }, + padding: { + left: 2, + right: 2, + top: 4, + bottom: 4, + }, + cornerRadius: 6, + }, + avatarRibbon: { + height: 3, + width: 12, + // TODO: Chore: Make avatarRibbon colors driven by the theme rather than being hard coded. + }, - // Sign in buttom - // FlatButton, Variant - signInPrompt: { - ...titlebarButton - }, + // Sign in buttom + // FlatButton, Variant + signInPrompt: { + ...titlebarButton, + }, - // Offline Indicator - offlineIcon: { - color: foreground(layer, "variant"), - width: 16, - margin: { - left: titlebarPadding, - }, - padding: { - right: 4, + // Offline Indicator + offlineIcon: { + color: foreground(layer, "variant"), + width: 16, + margin: { + left: itemSpacing, + }, + padding: { + right: 4, + }, + }, + + // Notice that the collaboration server is out of date + outdatedWarning: { + ...text(layer, "sans", "warning", { size: "xs" }), + background: withOpacity(background(layer, "warning"), 0.3), + border: border(layer, "warning"), + margin: { + left: itemSpacing, + }, + padding: { + left: 8, + right: 8, + }, + cornerRadius: 6, + }, + callControl: { + cornerRadius: 6, + color: foreground(layer, "variant"), + iconWidth: 12, + buttonWidth: 20, + hover: { + background: background(layer, "variant", "hovered"), + color: foreground(layer, "variant", "hovered"), + }, + }, + toggleContactsButton: { + margin: { left: itemSpacing }, + cornerRadius: 6, + color: foreground(layer, "variant"), + iconWidth: 8, + buttonWidth: 20, + active: { + background: background(layer, "variant", "active"), + color: foreground(layer, "variant", "active"), + }, + clicked: { + background: background(layer, "variant", "pressed"), + color: foreground(layer, "variant", "pressed"), + }, + hover: { + background: background(layer, "variant", "hovered"), + color: foreground(layer, "variant", "hovered"), + }, + }, + userMenuButton: { + buttonWidth: 20, + iconWidth: 12, + ...titlebarButton, + }, + toggleContactsBadge: { + cornerRadius: 3, + padding: 2, + margin: { top: 3, left: 3 }, + border: border(layer), + background: foreground(layer, "accent"), + }, + shareButton: { + ...titlebarButton, + }, }, - }, - // Notice that the collaboration server is out of date - outdatedWarning: { - ...text(layer, "sans", "warning", { size: "xs" }), - background: withOpacity(background(layer, "warning"), 0.3), - border: border(layer, "warning"), - margin: { - left: titlebarPadding, + toolbar: { + height: 34, + background: background(colorScheme.highest), + border: border(colorScheme.highest, { bottom: true }), + itemSpacing: 8, + navButton: { + color: foreground(colorScheme.highest, "on"), + iconWidth: 12, + buttonWidth: 24, + cornerRadius: 6, + hover: { + color: foreground(colorScheme.highest, "on", "hovered"), + background: background( + colorScheme.highest, + "on", + "hovered" + ), + }, + disabled: { + color: foreground(colorScheme.highest, "on", "disabled"), + }, + }, + padding: { left: 8, right: 8, top: 4, bottom: 4 }, }, - padding: { - left: 8, - right: 8, + breadcrumbs: { + ...text(layer, "mono", "variant"), + padding: { left: 6 }, }, - cornerRadius: 6, - }, - callControl: { - cornerRadius: 6, - color: foreground(layer, "variant"), - iconWidth: 12, - buttonWidth: 20, - hover: { - background: background(layer, "variant", "hovered"), - color: foreground(layer, "variant", "hovered"), + disconnectedOverlay: { + ...text(layer, "sans"), + background: withOpacity(background(layer), 0.8), }, - }, - toggleContactsButton: { - margin: { left: 6 }, - cornerRadius: 6, - color: foreground(layer, "variant"), - iconWidth: 8, - buttonWidth: 20, - active: { - background: background(layer, "variant", "active"), - color: foreground(layer, "variant", "active"), + notification: { + margin: { top: 10 }, + background: background(colorScheme.middle), + cornerRadius: 6, + padding: 12, + border: border(colorScheme.middle), + shadow: colorScheme.popoverShadow, }, - hover: { - background: background(layer, "variant", "hovered"), - color: foreground(layer, "variant", "hovered"), - }, - }, - toggleContactsBadge: { - cornerRadius: 3, - padding: 2, - margin: { top: 3, left: 3 }, - border: border(layer), - background: foreground(layer, "accent"), - }, - shareButton: { - ...titlebarButton - } - }, - - toolbar: { - height: 34, - background: background(colorScheme.highest), - border: border(colorScheme.highest, { bottom: true }), - itemSpacing: 8, - navButton: { - color: foreground(colorScheme.highest, "on"), - iconWidth: 12, - buttonWidth: 24, - cornerRadius: 6, - hover: { - color: foreground(colorScheme.highest, "on", "hovered"), - background: background(colorScheme.highest, "on", "hovered"), - }, - disabled: { - color: foreground(colorScheme.highest, "on", "disabled"), - }, - }, - padding: { left: 8, right: 8, top: 4, bottom: 4 }, - }, - breadcrumbs: { - ...text(layer, "mono", "variant"), - padding: { left: 6 }, - }, - disconnectedOverlay: { - ...text(layer, "sans"), - background: withOpacity(background(layer), 0.8), - }, - notification: { - margin: { top: 10 }, - background: background(colorScheme.middle), - cornerRadius: 6, - padding: 12, - border: border(colorScheme.middle), - shadow: colorScheme.popoverShadow, - }, - notifications: { - width: 400, - margin: { right: 10, bottom: 10 }, - }, - dock: { - initialSizeRight: 640, - initialSizeBottom: 480, - wash_color: withOpacity(background(colorScheme.highest), 0.5), - panel: { - border: border(colorScheme.middle), - }, - maximized: { - margin: 32, - border: border(colorScheme.highest, { overlay: true }), - shadow: colorScheme.modalShadow, - }, - }, - dropTargetOverlayColor: withOpacity( - foreground(layer, "variant"), - 0.5 - ), - }; + notifications: { + width: 400, + margin: { right: 10, bottom: 10 }, + }, + dock: { + initialSizeRight: 640, + initialSizeBottom: 480, + wash_color: withOpacity(background(colorScheme.highest), 0.5), + panel: { + border: border(colorScheme.middle), + }, + maximized: { + margin: 32, + border: border(colorScheme.highest, { overlay: true }), + shadow: colorScheme.modalShadow, + }, + }, + dropTargetOverlayColor: withOpacity(foreground(layer, "variant"), 0.5), + } } diff --git a/styles/src/system/lib/convert.ts b/styles/src/system/lib/convert.ts new file mode 100644 index 0000000000000000000000000000000000000000..998f95a636383d9e2c622133f2e2c09a17336672 --- /dev/null +++ b/styles/src/system/lib/convert.ts @@ -0,0 +1,11 @@ +/** Converts a percentage scale value (0-100) to normalized scale (0-1) value. */ +export function percentageToNormalized(value: number) { + const normalized = value / 100 + return normalized +} + +/** Converts a normalized scale (0-1) value to a percentage scale (0-100) value. */ +export function normalizedToPercetage(value: number) { + const percentage = value * 100 + return percentage +} diff --git a/styles/src/system/lib/curve.ts b/styles/src/system/lib/curve.ts new file mode 100644 index 0000000000000000000000000000000000000000..b24f2948cf3f2b6e4c47ee36d4dd5ed9934fa28b --- /dev/null +++ b/styles/src/system/lib/curve.ts @@ -0,0 +1,26 @@ +import bezier from "bezier-easing" +import { Curve } from "../ref/curves" + +/** + * Formats our Curve data structure into a bezier easing function. + * @param {Curve} curve - The curve to format. + * @param {Boolean} inverted - Whether or not to invert the curve. + * @returns {EasingFunction} The formatted easing function. + */ +export function curve(curve: Curve, inverted?: Boolean) { + if (inverted) { + return bezier( + curve.value[3], + curve.value[2], + curve.value[1], + curve.value[0] + ) + } + + return bezier( + curve.value[0], + curve.value[1], + curve.value[2], + curve.value[3] + ) +} diff --git a/styles/src/system/lib/generate.ts b/styles/src/system/lib/generate.ts new file mode 100644 index 0000000000000000000000000000000000000000..40f7a9154c18d7b7a6cabba474eb2befb56af492 --- /dev/null +++ b/styles/src/system/lib/generate.ts @@ -0,0 +1,159 @@ +import bezier from "bezier-easing" +import chroma from "chroma-js" +import { Color, ColorFamily, ColorFamilyConfig, ColorScale } from "../types" +import { percentageToNormalized } from "./convert" +import { curve } from "./curve" + +// Re-export interface in a more standard format +export type EasingFunction = bezier.EasingFunction + +/** + * Generates a color, outputs it in multiple formats, and returns a variety of useful metadata. + * + * @param {EasingFunction} hueEasing - An easing function for the hue component of the color. + * @param {EasingFunction} saturationEasing - An easing function for the saturation component of the color. + * @param {EasingFunction} lightnessEasing - An easing function for the lightness component of the color. + * @param {ColorFamilyConfig} family - Configuration for the color family. + * @param {number} step - The current step. + * @param {number} steps - The total number of steps in the color scale. + * + * @returns {Color} The generated color, with its calculated contrast against black and white, as well as its LCH values, RGBA array, hexadecimal representation, and a flag indicating if it is light or dark. + */ +function generateColor( + hueEasing: EasingFunction, + saturationEasing: EasingFunction, + lightnessEasing: EasingFunction, + family: ColorFamilyConfig, + step: number, + steps: number +) { + const { hue, saturation, lightness } = family.color + + const stepHue = hueEasing(step / steps) * (hue.end - hue.start) + hue.start + const stepSaturation = + saturationEasing(step / steps) * (saturation.end - saturation.start) + + saturation.start + const stepLightness = + lightnessEasing(step / steps) * (lightness.end - lightness.start) + + lightness.start + + const color = chroma.hsl( + stepHue, + percentageToNormalized(stepSaturation), + percentageToNormalized(stepLightness) + ) + + const contrast = { + black: { + value: chroma.contrast(color, "black"), + aaPass: chroma.contrast(color, "black") >= 4.5, + aaaPass: chroma.contrast(color, "black") >= 7, + }, + white: { + value: chroma.contrast(color, "white"), + aaPass: chroma.contrast(color, "white") >= 4.5, + aaaPass: chroma.contrast(color, "white") >= 7, + }, + } + + const lch = color.lch() + const rgba = color.rgba() + const hex = color.hex() + + // 55 is a magic number. It's the lightness value at which we consider a color to be "light". + // It was picked by eye with some testing. We might want to use a more scientific approach in the future. + const isLight = lch[0] > 55 + + const result: Color = { + step, + lch, + hex, + rgba, + contrast, + isLight, + } + + return result +} + +/** + * Generates a color scale based on a color family configuration. + * + * @param {ColorFamilyConfig} config - The configuration for the color family. + * @param {Boolean} inverted - Specifies whether the color scale should be inverted or not. + * + * @returns {ColorScale} The generated color scale. + * + * @example + * ```ts + * const colorScale = generateColorScale({ + * name: "blue", + * color: { + * hue: { + * start: 210, + * end: 240, + * curve: "easeInOut" + * }, + * saturation: { + * start: 100, + * end: 100, + * curve: "easeInOut" + * }, + * lightness: { + * start: 50, + * end: 50, + * curve: "easeInOut" + * } + * } + * }); + * ``` + */ + +export function generateColorScale( + config: ColorFamilyConfig, + inverted: Boolean = false +) { + const { hue, saturation, lightness } = config.color + + // 101 steps means we get values from 0-100 + const NUM_STEPS = 101 + + const hueEasing = curve(hue.curve, inverted) + const saturationEasing = curve(saturation.curve, inverted) + const lightnessEasing = curve(lightness.curve, inverted) + + let scale: ColorScale = { + colors: [], + values: [], + } + + for (let i = 0; i < NUM_STEPS; i++) { + const color = generateColor( + hueEasing, + saturationEasing, + lightnessEasing, + config, + i, + NUM_STEPS + ) + + scale.colors.push(color) + scale.values.push(color.hex) + } + + return scale +} + +/** Generates a color family with a scale and an inverted scale. */ +export function generateColorFamily(config: ColorFamilyConfig) { + const scale = generateColorScale(config, false) + const invertedScale = generateColorScale(config, true) + + const family: ColorFamily = { + name: config.name, + scale, + invertedScale, + } + + return family +} diff --git a/styles/src/system/ref/color.ts b/styles/src/system/ref/color.ts new file mode 100644 index 0000000000000000000000000000000000000000..6c0b53c35b08284643d316bbb0f49161c8ab25f8 --- /dev/null +++ b/styles/src/system/ref/color.ts @@ -0,0 +1,445 @@ +import { generateColorFamily } from "../lib/generate" +import { curve } from "./curves" + +// These are the source colors for the color scales in the system. +// These should never directly be used directly in components or themes as they generate thousands of lines of code. +// Instead, use the outputs from the reference palette which exports a smaller subset of colors. + +// Token or user-facing colors should use short, clear names and a 100-900 scale to match the font weight scale. + +// Light Gray ======================================== // + +export const lightgray = generateColorFamily({ + name: "lightgray", + color: { + hue: { + start: 210, + end: 210, + curve: curve.linear, + }, + saturation: { + start: 10, + end: 15, + curve: curve.saturation, + }, + lightness: { + start: 97, + end: 50, + curve: curve.linear, + }, + }, +}) + +// Light Dark ======================================== // + +export const darkgray = generateColorFamily({ + name: "darkgray", + color: { + hue: { + start: 210, + end: 210, + curve: curve.linear, + }, + saturation: { + start: 15, + end: 20, + curve: curve.saturation, + }, + lightness: { + start: 55, + end: 8, + curve: curve.linear, + }, + }, +}) + +// Red ======================================== // + +export const red = generateColorFamily({ + name: "red", + color: { + hue: { + start: 0, + end: 0, + curve: curve.linear, + }, + saturation: { + start: 95, + end: 75, + curve: curve.saturation, + }, + lightness: { + start: 97, + end: 25, + curve: curve.lightness, + }, + }, +}) + +// Sunset ======================================== // + +export const sunset = generateColorFamily({ + name: "sunset", + color: { + hue: { + start: 15, + end: 15, + curve: curve.linear, + }, + saturation: { + start: 100, + end: 90, + curve: curve.saturation, + }, + lightness: { + start: 97, + end: 25, + curve: curve.lightness, + }, + }, +}) + +// Orange ======================================== // + +export const orange = generateColorFamily({ + name: "orange", + color: { + hue: { + start: 25, + end: 25, + curve: curve.linear, + }, + saturation: { + start: 100, + end: 95, + curve: curve.saturation, + }, + lightness: { + start: 97, + end: 20, + curve: curve.lightness, + }, + }, +}) + +// Amber ======================================== // + +export const amber = generateColorFamily({ + name: "amber", + color: { + hue: { + start: 38, + end: 38, + curve: curve.linear, + }, + saturation: { + start: 100, + end: 100, + curve: curve.saturation, + }, + lightness: { + start: 97, + end: 18, + curve: curve.lightness, + }, + }, +}) + +// Yellow ======================================== // + +export const yellow = generateColorFamily({ + name: "yellow", + color: { + hue: { + start: 48, + end: 48, + curve: curve.linear, + }, + saturation: { + start: 90, + end: 100, + curve: curve.saturation, + }, + lightness: { + start: 97, + end: 15, + curve: curve.lightness, + }, + }, +}) + +// Lemon ======================================== // + +export const lemon = generateColorFamily({ + name: "lemon", + color: { + hue: { + start: 55, + end: 55, + curve: curve.linear, + }, + saturation: { + start: 85, + end: 95, + curve: curve.saturation, + }, + lightness: { + start: 97, + end: 15, + curve: curve.lightness, + }, + }, +}) + +// Citron ======================================== // + +export const citron = generateColorFamily({ + name: "citron", + color: { + hue: { + start: 70, + end: 70, + curve: curve.linear, + }, + saturation: { + start: 85, + end: 90, + curve: curve.saturation, + }, + lightness: { + start: 97, + end: 15, + curve: curve.lightness, + }, + }, +}) + +// Lime ======================================== // + +export const lime = generateColorFamily({ + name: "lime", + color: { + hue: { + start: 85, + end: 85, + curve: curve.linear, + }, + saturation: { + start: 85, + end: 80, + curve: curve.saturation, + }, + lightness: { + start: 97, + end: 18, + curve: curve.lightness, + }, + }, +}) + +// Green ======================================== // + +export const green = generateColorFamily({ + name: "green", + color: { + hue: { + start: 108, + end: 108, + curve: curve.linear, + }, + saturation: { + start: 60, + end: 70, + curve: curve.saturation, + }, + lightness: { + start: 97, + end: 18, + curve: curve.lightness, + }, + }, +}) + +// Mint ======================================== // + +export const mint = generateColorFamily({ + name: "mint", + color: { + hue: { + start: 142, + end: 142, + curve: curve.linear, + }, + saturation: { + start: 60, + end: 75, + curve: curve.saturation, + }, + lightness: { + start: 97, + end: 20, + curve: curve.lightness, + }, + }, +}) + +// Cyan ======================================== // + +export const cyan = generateColorFamily({ + name: "cyan", + color: { + hue: { + start: 179, + end: 179, + curve: curve.linear, + }, + saturation: { + start: 70, + end: 80, + curve: curve.saturation, + }, + lightness: { + start: 97, + end: 20, + curve: curve.lightness, + }, + }, +}) + +// Sky ======================================== // + +export const sky = generateColorFamily({ + name: "sky", + color: { + hue: { + start: 195, + end: 205, + curve: curve.linear, + }, + saturation: { + start: 85, + end: 90, + curve: curve.saturation, + }, + lightness: { + start: 97, + end: 15, + curve: curve.lightness, + }, + }, +}) + +// Blue ======================================== // + +export const blue = generateColorFamily({ + name: "blue", + color: { + hue: { + start: 218, + end: 218, + curve: curve.linear, + }, + saturation: { + start: 85, + end: 70, + curve: curve.saturation, + }, + lightness: { + start: 97, + end: 15, + curve: curve.lightness, + }, + }, +}) + +// Indigo ======================================== // + +export const indigo = generateColorFamily({ + name: "indigo", + color: { + hue: { + start: 245, + end: 245, + curve: curve.linear, + }, + saturation: { + start: 60, + end: 50, + curve: curve.saturation, + }, + lightness: { + start: 97, + end: 22, + curve: curve.lightness, + }, + }, +}) + +// Purple ======================================== // + +export const purple = generateColorFamily({ + name: "purple", + color: { + hue: { + start: 260, + end: 270, + curve: curve.linear, + }, + saturation: { + start: 65, + end: 55, + curve: curve.saturation, + }, + lightness: { + start: 97, + end: 20, + curve: curve.lightness, + }, + }, +}) + +// Pink ======================================== // + +export const pink = generateColorFamily({ + name: "pink", + color: { + hue: { + start: 320, + end: 330, + curve: curve.linear, + }, + saturation: { + start: 70, + end: 65, + curve: curve.saturation, + }, + lightness: { + start: 97, + end: 32, + curve: curve.lightness, + }, + }, +}) + +// Rose ======================================== // + +export const rose = generateColorFamily({ + name: "rose", + color: { + hue: { + start: 345, + end: 345, + curve: curve.linear, + }, + saturation: { + start: 90, + end: 70, + curve: curve.saturation, + }, + lightness: { + start: 97, + end: 32, + curve: curve.lightness, + }, + }, +}) diff --git a/styles/src/system/ref/curves.ts b/styles/src/system/ref/curves.ts new file mode 100644 index 0000000000000000000000000000000000000000..02002dbe9befd605b3a57400684b2f37bf7b642b --- /dev/null +++ b/styles/src/system/ref/curves.ts @@ -0,0 +1,25 @@ +export interface Curve { + name: string + value: number[] +} + +export interface Curves { + lightness: Curve + saturation: Curve + linear: Curve +} + +export const curve: Curves = { + lightness: { + name: "lightnessCurve", + value: [0.2, 0, 0.75, 1.0], + }, + saturation: { + name: "saturationCurve", + value: [0.67, 0.6, 0.55, 1.0], + }, + linear: { + name: "linear", + value: [0.5, 0.5, 0.5, 0.5], + }, +} diff --git a/styles/src/system/system.ts b/styles/src/system/system.ts new file mode 100644 index 0000000000000000000000000000000000000000..619b0795c89770920b886c20a508da23d9b6eed9 --- /dev/null +++ b/styles/src/system/system.ts @@ -0,0 +1,32 @@ +import chroma from "chroma-js" +import * as colorFamily from "./ref/color" + +const color = { + lightgray: chroma + .scale(colorFamily.lightgray.scale.values) + .mode("lch") + .colors(9), + darkgray: chroma + .scale(colorFamily.darkgray.scale.values) + .mode("lch") + .colors(9), + red: chroma.scale(colorFamily.red.scale.values).mode("lch").colors(9), + sunset: chroma.scale(colorFamily.sunset.scale.values).mode("lch").colors(9), + orange: chroma.scale(colorFamily.orange.scale.values).mode("lch").colors(9), + amber: chroma.scale(colorFamily.amber.scale.values).mode("lch").colors(9), + yellow: chroma.scale(colorFamily.yellow.scale.values).mode("lch").colors(9), + lemon: chroma.scale(colorFamily.lemon.scale.values).mode("lch").colors(9), + citron: chroma.scale(colorFamily.citron.scale.values).mode("lch").colors(9), + lime: chroma.scale(colorFamily.lime.scale.values).mode("lch").colors(9), + green: chroma.scale(colorFamily.green.scale.values).mode("lch").colors(9), + mint: chroma.scale(colorFamily.mint.scale.values).mode("lch").colors(9), + cyan: chroma.scale(colorFamily.cyan.scale.values).mode("lch").colors(9), + sky: chroma.scale(colorFamily.sky.scale.values).mode("lch").colors(9), + blue: chroma.scale(colorFamily.blue.scale.values).mode("lch").colors(9), + indigo: chroma.scale(colorFamily.indigo.scale.values).mode("lch").colors(9), + purple: chroma.scale(colorFamily.purple.scale.values).mode("lch").colors(9), + pink: chroma.scale(colorFamily.pink.scale.values).mode("lch").colors(9), + rose: chroma.scale(colorFamily.rose.scale.values).mode("lch").colors(9), +} + +export { color } diff --git a/styles/src/system/types.ts b/styles/src/system/types.ts new file mode 100644 index 0000000000000000000000000000000000000000..8bfa2dd7dbf9608f4549fdc2adde412c7da5022d --- /dev/null +++ b/styles/src/system/types.ts @@ -0,0 +1,66 @@ +import { Curve } from "./ref/curves" + +export interface ColorAccessiblityValue { + value: number + aaPass: boolean + aaaPass: boolean +} + +/** + * Calculates the color contrast between a specified color and its corresponding background and foreground colors. + * + * @note This implementation is currently basic – Currently we only calculate contrasts against black and white, in the future will allow for dynamic color contrast calculation based on the colors present in a given palette. + * @note The goal is to align with WCAG3 accessibility standards as they become stabilized. See the [WCAG 3 Introduction](https://www.w3.org/WAI/standards-guidelines/wcag/wcag3-intro/) for more information. + */ +export interface ColorAccessiblity { + black: ColorAccessiblityValue + white: ColorAccessiblityValue +} + +export type Color = { + step: number + contrast: ColorAccessiblity + hex: string + lch: number[] + rgba: number[] + isLight: boolean +} + +export interface ColorScale { + colors: Color[] + // An array of hex values for each color in the scale + values: string[] +} + +export type ColorFamily = { + name: string + scale: ColorScale + invertedScale: ColorScale +} + +export interface ColorFamilyHue { + start: number + end: number + curve: Curve +} + +export interface ColorFamilySaturation { + start: number + end: number + curve: Curve +} + +export interface ColorFamilyLightness { + start: number + end: number + curve: Curve +} + +export interface ColorFamilyConfig { + name: string + color: { + hue: ColorFamilyHue + saturation: ColorFamilySaturation + lightness: ColorFamilyLightness + } +} diff --git a/styles/src/themes/andromeda.ts b/styles/src/themes/andromeda.ts index b76179b3c5e5b5b5c7d231aca32afb318b9a22d1..7e715f61ac451e4e205618d542d1100605b1940a 100644 --- a/styles/src/themes/andromeda.ts +++ b/styles/src/themes/andromeda.ts @@ -1,41 +1,43 @@ -import chroma from "chroma-js"; -import { Meta } from "./common/colorScheme"; -import { colorRamp, createColorScheme } from "./common/ramps"; +import chroma from "chroma-js" +import { Meta } from "./common/colorScheme" +import { colorRamp, createColorScheme } from "./common/ramps" -const name = "Andromeda"; +const name = "Andromeda" const ramps = { - 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")), - green: colorRamp(chroma("#96E072")), - cyan: colorRamp(chroma("#00E8C6")), - blue: colorRamp(chroma("#0CA793")), - violet: colorRamp(chroma("#8A3FA6")), - magenta: colorRamp(chroma("#C74DED")), -}; + 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")), + green: colorRamp(chroma("#96E072")), + cyan: colorRamp(chroma("#00E8C6")), + blue: colorRamp(chroma("#0CA793")), + violet: colorRamp(chroma("#8A3FA6")), + magenta: colorRamp(chroma("#C74DED")), +} -export const dark = createColorScheme(`${name}`, false, ramps); +export const dark = createColorScheme(`${name}`, false, ramps) export const meta: Meta = { - name, - author: "EliverLara", - license: { - SPDX: "MIT", - https_url: "https://raw.githubusercontent.com/EliverLara/Andromeda/master/LICENSE.md", - license_checksum: "2f7886f1a05cefc2c26f5e49de1a39fa4466413c1ccb06fc80960e73f5ed4b89" - }, - url: "https://github.com/EliverLara/Andromeda" -} \ No newline at end of file + name, + author: "EliverLara", + license: { + SPDX: "MIT", + https_url: + "https://raw.githubusercontent.com/EliverLara/Andromeda/master/LICENSE.md", + license_checksum: + "2f7886f1a05cefc2c26f5e49de1a39fa4466413c1ccb06fc80960e73f5ed4b89", + }, + url: "https://github.com/EliverLara/Andromeda", +} diff --git a/styles/src/themes/atelier-cave.ts b/styles/src/themes/atelier-cave.ts index 0959cabacee3715c3473be466efd8fe45b44cd69..7a85db97e9fe3d19aaa9bb5b21e5636b3ed385dc 100644 --- a/styles/src/themes/atelier-cave.ts +++ b/styles/src/themes/atelier-cave.ts @@ -1,63 +1,63 @@ -import chroma from "chroma-js"; -import { Meta } from "./common/colorScheme"; -import { colorRamp, createColorScheme } from "./common/ramps"; +import chroma from "chroma-js" +import { Meta } from "./common/colorScheme" +import { colorRamp, createColorScheme } from "./common/ramps" -const name = "Atelier Cave"; +const name = "Atelier Cave" 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")), - green: colorRamp(chroma("#2a9292")), - cyan: colorRamp(chroma("#398bc6")), - blue: colorRamp(chroma("#576ddb")), - violet: colorRamp(chroma("#955ae7")), - magenta: colorRamp(chroma("#bf40bf")), -}); + 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")), + green: colorRamp(chroma("#2a9292")), + cyan: colorRamp(chroma("#398bc6")), + blue: colorRamp(chroma("#576ddb")), + violet: colorRamp(chroma("#955ae7")), + magenta: colorRamp(chroma("#bf40bf")), +}) 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")), -}); - + 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")), +}) export const meta: Meta = { - name, - author: "atelierbram", - license: { - SPDX: "MIT", - https_url: "https://atelierbram.mit-license.org/license.txt", - license_checksum: "f95ce526ef4e7eecf7a832bba0e3451cc1000f9ce63eb01ed6f64f8109f5d0a5" - }, - url: "https://atelierbram.github.io/syntax-highlighting/atelier-schemes/cave/" -} \ No newline at end of file + name, + author: "atelierbram", + license: { + SPDX: "MIT", + https_url: "https://atelierbram.mit-license.org/license.txt", + license_checksum: + "f95ce526ef4e7eecf7a832bba0e3451cc1000f9ce63eb01ed6f64f8109f5d0a5", + }, + url: "https://atelierbram.github.io/syntax-highlighting/atelier-schemes/cave/", +} diff --git a/styles/src/themes/atelier-sulphurpool.ts b/styles/src/themes/atelier-sulphurpool.ts index fa51b1ec80172c3a00c063d9ed7384596db81306..bfd105172a0c59240f83495f277848a7c5bc2eac 100644 --- a/styles/src/themes/atelier-sulphurpool.ts +++ b/styles/src/themes/atelier-sulphurpool.ts @@ -1,42 +1,43 @@ -import chroma from "chroma-js"; -import { Meta } from "./common/colorScheme"; -import { colorRamp, createColorScheme } from "./common/ramps"; +import chroma from "chroma-js" +import { Meta } from "./common/colorScheme" +import { colorRamp, createColorScheme } from "./common/ramps" -const name = "Atelier Sulphurpool"; +const name = "Atelier Sulphurpool" const ramps = { - 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")), - green: colorRamp(chroma("#ac9739")), - cyan: colorRamp(chroma("#22a2c9")), - blue: colorRamp(chroma("#3d8fd1")), - violet: colorRamp(chroma("#6679cc")), - magenta: colorRamp(chroma("#9c637a")), -}; + 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")), + green: colorRamp(chroma("#ac9739")), + cyan: colorRamp(chroma("#22a2c9")), + blue: colorRamp(chroma("#3d8fd1")), + violet: colorRamp(chroma("#6679cc")), + magenta: colorRamp(chroma("#9c637a")), +} -export const dark = createColorScheme(`${name} Dark`, false, ramps); -export const light = createColorScheme(`${name} Light`, true, ramps); +export const dark = createColorScheme(`${name} Dark`, false, ramps) +export const light = createColorScheme(`${name} Light`, true, ramps) export const meta: Meta = { - name, - author: "atelierbram", - license: { - SPDX: "MIT", - https_url: "https://atelierbram.mit-license.org/license.txt", - license_checksum: "f95ce526ef4e7eecf7a832bba0e3451cc1000f9ce63eb01ed6f64f8109f5d0a5" - }, - url: "https://atelierbram.github.io/syntax-highlighting/atelier-schemes/sulphurpool/" -} \ No newline at end of file + name, + author: "atelierbram", + license: { + SPDX: "MIT", + https_url: "https://atelierbram.mit-license.org/license.txt", + license_checksum: + "f95ce526ef4e7eecf7a832bba0e3451cc1000f9ce63eb01ed6f64f8109f5d0a5", + }, + url: "https://atelierbram.github.io/syntax-highlighting/atelier-schemes/sulphurpool/", +} diff --git a/styles/src/themes/common/base16.ts b/styles/src/themes/common/base16.ts deleted file mode 100644 index 23ccc57fd458a0c3fa414bd07aaee007f0a06ae5..0000000000000000000000000000000000000000 --- a/styles/src/themes/common/base16.ts +++ /dev/null @@ -1,296 +0,0 @@ -// NOTE – This should be removed -// I (Nate) need to come back and check if we are still using this anywhere - -import chroma, { Color, Scale } from "chroma-js"; -import { fontWeights } from "../../common"; -import { withOpacity } from "../../utils/color"; -import Theme, { buildPlayer, Syntax } from "./theme"; - -export function colorRamp(color: Color): Scale { - let hue = color.hsl()[0]; - let endColor = chroma.hsl(hue, 0.88, 0.96); - let startColor = chroma.hsl(hue, 0.68, 0.12); - return chroma.scale([startColor, color, endColor]).mode("hsl"); -} - -export function createTheme( - name: string, - isLight: boolean, - color_ramps: { [rampName: string]: Scale } -): Theme { - let ramps: typeof color_ramps = {}; - // 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 color_ramps) { - ramps[rampName] = chroma - .scale((color_ramps[rampName].colors as any)()) - .domain([1, 0]); - } - ramps.neutral = chroma - .scale((color_ramps.neutral.colors as any)()) - .domain([7, 0]); - } else { - for (var rampName in color_ramps) { - ramps[rampName] = chroma - .scale((color_ramps[rampName].colors as any)()) - .domain([0, 1]); - } - ramps.neutral = chroma - .scale((color_ramps.neutral.colors as any)()) - .domain([0, 7]); - } - - let blend = isLight ? 0.12 : 0.24; - - function sample(ramp: Scale, index: number): string { - return ramp(index).hex(); - } - const darkest = ramps.neutral(isLight ? 7 : 0).hex(); - - const backgroundColor = { - // Title bar - 100: { - base: sample(ramps.neutral, 1.25), - hovered: sample(ramps.neutral, 1.5), - active: sample(ramps.neutral, 1.75), - }, - // Midground (panels, etc) - 300: { - base: sample(ramps.neutral, 1), - hovered: sample(ramps.neutral, 1.25), - active: sample(ramps.neutral, 1.5), - }, - // Editor - 500: { - base: sample(ramps.neutral, 0), - hovered: sample(ramps.neutral, 0.25), - active: sample(ramps.neutral, 0.5), - }, - on300: { - base: sample(ramps.neutral, 0), - hovered: sample(ramps.neutral, 0.5), - active: sample(ramps.neutral, 1), - }, - on500: { - base: sample(ramps.neutral, 1), - hovered: sample(ramps.neutral, 1.5), - active: sample(ramps.neutral, 2), - }, - ok: { - base: withOpacity(sample(ramps.green, 0.5), 0.15), - hovered: withOpacity(sample(ramps.green, 0.5), 0.2), - active: withOpacity(sample(ramps.green, 0.5), 0.25), - }, - error: { - base: withOpacity(sample(ramps.red, 0.5), 0.15), - hovered: withOpacity(sample(ramps.red, 0.5), 0.2), - active: withOpacity(sample(ramps.red, 0.5), 0.25), - }, - on500Error: { - base: sample(ramps.red, 0.05), - hovered: sample(ramps.red, 0.1), - active: sample(ramps.red, 0.15), - }, - warning: { - base: withOpacity(sample(ramps.yellow, 0.5), 0.15), - hovered: withOpacity(sample(ramps.yellow, 0.5), 0.2), - active: withOpacity(sample(ramps.yellow, 0.5), 0.25), - }, - on500Warning: { - base: sample(ramps.yellow, 0.05), - hovered: sample(ramps.yellow, 0.1), - active: sample(ramps.yellow, 0.15), - }, - info: { - base: withOpacity(sample(ramps.blue, 0.5), 0.15), - hovered: withOpacity(sample(ramps.blue, 0.5), 0.2), - active: withOpacity(sample(ramps.blue, 0.5), 0.25), - }, - on500Info: { - base: sample(ramps.blue, 0.05), - hovered: sample(ramps.blue, 0.1), - active: sample(ramps.blue, 0.15), - }, - on500Ok: { - base: sample(ramps.green, 0.05), - hovered: sample(ramps.green, 0.1), - active: sample(ramps.green, 0.15), - }, - }; - - const borderColor = { - primary: sample(ramps.neutral, isLight ? 1.5 : 0), - secondary: sample(ramps.neutral, isLight ? 1.25 : 1), - muted: sample(ramps.neutral, isLight ? 1.25 : 3), - active: sample(ramps.neutral, isLight ? 4 : 3), - onMedia: withOpacity(darkest, 0.1), - ok: sample(ramps.green, 0.3), - error: sample(ramps.red, 0.3), - warning: sample(ramps.yellow, 0.3), - info: sample(ramps.blue, 0.3), - }; - - const textColor = { - primary: sample(ramps.neutral, 6), - secondary: sample(ramps.neutral, 5), - muted: sample(ramps.neutral, 4), - placeholder: sample(ramps.neutral, 3), - active: sample(ramps.neutral, 7), - feature: sample(ramps.blue, 0.5), - ok: sample(ramps.green, 0.5), - error: sample(ramps.red, 0.5), - warning: sample(ramps.yellow, 0.5), - info: sample(ramps.blue, 0.5), - onMedia: darkest, - }; - - const player = { - 1: buildPlayer(sample(ramps.blue, 0.5)), - 2: buildPlayer(sample(ramps.green, 0.5)), - 3: buildPlayer(sample(ramps.magenta, 0.5)), - 4: buildPlayer(sample(ramps.orange, 0.5)), - 5: buildPlayer(sample(ramps.violet, 0.5)), - 6: buildPlayer(sample(ramps.cyan, 0.5)), - 7: buildPlayer(sample(ramps.red, 0.5)), - 8: buildPlayer(sample(ramps.yellow, 0.5)), - }; - - const editor = { - background: backgroundColor[500].base, - indent_guide: borderColor.muted, - indent_guide_active: borderColor.secondary, - line: { - active: sample(ramps.neutral, 1), - highlighted: sample(ramps.neutral, 1.25), // TODO: Where is this used? - }, - highlight: { - selection: player[1].selectionColor, - occurrence: withOpacity(sample(ramps.neutral, 3.5), blend), - activeOccurrence: withOpacity(sample(ramps.neutral, 3.5), blend * 2), // TODO: Not hooked up - https://github.com/zed-industries/zed/issues/751 - matchingBracket: backgroundColor[500].active, // TODO: Not hooked up - match: sample(ramps.violet, 0.15), - activeMatch: withOpacity(sample(ramps.violet, 0.4), blend * 2), // TODO: Not hooked up - https://github.com/zed-industries/zed/issues/751 - related: backgroundColor[500].hovered, - }, - gutter: { - primary: textColor.placeholder, - active: textColor.active, - }, - }; - - const syntax: Syntax = { - primary: { - color: sample(ramps.neutral, 7), - weight: fontWeights.normal, - }, - "variable.special": { - color: sample(ramps.blue, 0.8), - weight: fontWeights.normal, - }, - comment: { - color: sample(ramps.neutral, 5), - weight: fontWeights.normal, - }, - punctuation: { - color: sample(ramps.neutral, 6), - weight: fontWeights.normal, - }, - constant: { - color: sample(ramps.neutral, 4), - weight: fontWeights.normal, - }, - keyword: { - color: sample(ramps.blue, 0.5), - weight: fontWeights.normal, - }, - function: { - color: sample(ramps.yellow, 0.5), - weight: fontWeights.normal, - }, - type: { - color: sample(ramps.cyan, 0.5), - weight: fontWeights.normal, - }, - constructor: { - color: sample(ramps.cyan, 0.5), - weight: fontWeights.normal, - }, - property: { - color: sample(ramps.blue, 0.6), - weight: fontWeights.normal, - }, - enum: { - color: sample(ramps.orange, 0.5), - weight: fontWeights.normal, - }, - operator: { - color: sample(ramps.orange, 0.5), - weight: fontWeights.normal, - }, - string: { - color: sample(ramps.orange, 0.5), - weight: fontWeights.normal, - }, - number: { - color: sample(ramps.green, 0.5), - weight: fontWeights.normal, - }, - boolean: { - color: sample(ramps.green, 0.5), - weight: fontWeights.normal, - }, - predictive: { - color: textColor.muted, - weight: fontWeights.normal, - }, - title: { - color: sample(ramps.yellow, 0.5), - weight: fontWeights.bold, - }, - emphasis: { - color: textColor.feature, - weight: fontWeights.normal, - }, - "emphasis.strong": { - color: textColor.feature, - weight: fontWeights.bold, - }, - linkUri: { - color: sample(ramps.green, 0.5), - weight: fontWeights.normal, - underline: true, - }, - linkText: { - color: sample(ramps.orange, 0.5), - weight: fontWeights.normal, - italic: true, - }, - }; - - const shadow = withOpacity( - ramps - .neutral(isLight ? 7 : 0) - .darken() - .hex(), - blend - ); - - return { - name, - isLight, - backgroundColor, - borderColor, - textColor, - iconColor: textColor, - editor, - syntax, - player, - shadow, - ramps, - }; -} diff --git a/styles/src/themes/common/colorScheme.ts b/styles/src/themes/common/colorScheme.ts index ee858627d76a40f32121408be50a16152953c751..8de484d7f548155cbbcdfa727e443fcf534673ad 100644 --- a/styles/src/themes/common/colorScheme.ts +++ b/styles/src/themes/common/colorScheme.ts @@ -1,100 +1,138 @@ -import { Scale } from "chroma-js"; +import { Scale } from "chroma-js" +import { FontWeight } from "../../common" export interface ColorScheme { - name: string; - isLight: boolean; + name: string + isLight: boolean - lowest: Layer; - middle: Layer; - highest: Layer; + lowest: Layer + middle: Layer + highest: Layer - ramps: RampSet; + ramps: RampSet - popoverShadow: Shadow; - modalShadow: Shadow; + popoverShadow: Shadow + modalShadow: Shadow - players: Players; + players: Players + syntax?: Partial } export interface Meta { - name: string, - author: string, - url: string, - license: License + name: string + author: string + url: string + license: License } export interface License { - SPDX: SPDXExpression, - /// A url where we can download the license's text - https_url: string, - license_checksum: string + SPDX: SPDXExpression + /// A url where we can download the license's text + https_url: string + license_checksum: string } // License name -> License text export interface Licenses { - [key: string]: string + [key: string]: string } // FIXME: Add support for the SPDX expression syntax -export type SPDXExpression = "MIT"; +export type SPDXExpression = "MIT" export interface Player { - cursor: string; - selection: string; + cursor: string + selection: string } export interface Players { - "0": Player; - "1": Player; - "2": Player; - "3": Player; - "4": Player; - "5": Player; - "6": Player; - "7": Player; + "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[]; + blur: number + color: string + offset: number[] } -export type StyleSets = keyof Layer; +export type StyleSets = keyof Layer export interface Layer { - base: StyleSet; - variant: StyleSet; - on: StyleSet; - accent: StyleSet; - positive: StyleSet; - warning: StyleSet; - negative: StyleSet; + 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; + neutral: Scale + red: Scale + orange: Scale + yellow: Scale + green: Scale + cyan: Scale + blue: Scale + violet: Scale + magenta: Scale } -export type Styles = keyof StyleSet; +export type Styles = keyof StyleSet export interface StyleSet { - default: Style; - active: Style; - disabled: Style; - hovered: Style; - pressed: Style; - inverted: Style; + default: Style + active: Style + disabled: Style + hovered: Style + pressed: Style + inverted: Style } export interface Style { - background: string; - border: string; - foreground: string; + background: string + border: string + foreground: string } + +export interface SyntaxHighlightStyle { + color: string + weight?: FontWeight + underline?: boolean + italic?: boolean +} + +export interface Syntax { + primary: SyntaxHighlightStyle + "variable.special": SyntaxHighlightStyle + comment: SyntaxHighlightStyle + punctuation: SyntaxHighlightStyle + constant: SyntaxHighlightStyle + keyword: SyntaxHighlightStyle + function: SyntaxHighlightStyle + type: SyntaxHighlightStyle + constructor: SyntaxHighlightStyle + variant: SyntaxHighlightStyle + property: SyntaxHighlightStyle + enum: SyntaxHighlightStyle + operator: SyntaxHighlightStyle + string: SyntaxHighlightStyle + number: SyntaxHighlightStyle + boolean: SyntaxHighlightStyle + predictive: SyntaxHighlightStyle + title: SyntaxHighlightStyle + emphasis: SyntaxHighlightStyle + "emphasis.strong": SyntaxHighlightStyle + linkUri: SyntaxHighlightStyle + linkText: SyntaxHighlightStyle +} + +// HACK: "constructor" as a key in the syntax interface returns an error when a theme tries to use it. +// For now hack around it by omiting constructor as a valid key for overrides. +export type ThemeSyntax = Partial> diff --git a/styles/src/themes/common/ramps.ts b/styles/src/themes/common/ramps.ts index 971830ed076cda63f14d6645c877bc1df7b61473..746b102a841dbc180a09a857f32ac3619df2e762 100644 --- a/styles/src/themes/common/ramps.ts +++ b/styles/src/themes/common/ramps.ts @@ -1,210 +1,215 @@ -import chroma, { Color, Scale } from "chroma-js"; +import chroma, { Color, Scale } from "chroma-js" import { - ColorScheme, - Layer, - Player, - RampSet, - Style, - Styles, - StyleSet, -} from "./colorScheme"; + ColorScheme, + Layer, + Player, + RampSet, + Style, + Styles, + StyleSet, + ThemeSyntax, +} 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"); + 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 } + name: string, + isLight: boolean, + colorRamps: { [rampName: string]: Scale }, + syntax?: ThemeSyntax ): 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() - ); + // 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), } - 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)); + + return { + name, + isLight, + + ramps, + + lowest, + middle, + highest, + + popoverShadow, + modalShadow, + + players, + syntax, } - 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(), - }; + 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), - }; + 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), - }; + 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), - }; + 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 + 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(); + 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]), + } } - } - 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"), - }; + 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 + 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, - }, - }; + 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, + }, + } } diff --git a/styles/src/themes/common/theme.ts b/styles/src/themes/common/theme.ts deleted file mode 100644 index a787443f31b00487ef6c8463fd4045dd18a2baca..0000000000000000000000000000000000000000 --- a/styles/src/themes/common/theme.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { Scale } from "chroma-js"; -import { FontWeight } from "../../common"; -import { withOpacity } from "../../utils/color"; - -export interface SyntaxHighlightStyle { - color: string; - weight?: FontWeight; - underline?: boolean; - italic?: boolean; -} - -export interface Player { - baseColor: string; - cursorColor: string; - selectionColor: string; - borderColor: string; -} -export function buildPlayer( - color: string, - cursorOpacity?: number, - selectionOpacity?: number, - borderOpacity?: number -) { - return { - baseColor: color, - cursorColor: withOpacity(color, cursorOpacity || 1.0), - selectionColor: withOpacity(color, selectionOpacity || 0.24), - borderColor: withOpacity(color, borderOpacity || 0.8), - }; -} - -export interface BackgroundColorSet { - base: string; - hovered: string; - active: string; -} - -export interface Syntax { - primary: SyntaxHighlightStyle; - comment: SyntaxHighlightStyle; - punctuation: SyntaxHighlightStyle; - constant: SyntaxHighlightStyle; - keyword: SyntaxHighlightStyle; - function: SyntaxHighlightStyle; - type: SyntaxHighlightStyle; - constructor: SyntaxHighlightStyle; - property: SyntaxHighlightStyle; - enum: SyntaxHighlightStyle; - operator: SyntaxHighlightStyle; - string: SyntaxHighlightStyle; - number: SyntaxHighlightStyle; - boolean: SyntaxHighlightStyle; - predictive: SyntaxHighlightStyle; - title: SyntaxHighlightStyle; - emphasis: SyntaxHighlightStyle; - linkUri: SyntaxHighlightStyle; - linkText: SyntaxHighlightStyle; - - [key: string]: SyntaxHighlightStyle; -} - -export default interface Theme { - name: string; - isLight: boolean; - backgroundColor: { - // Basically just Title Bar - // Lowest background level - 100: BackgroundColorSet; - // Tab bars, panels, popovers - // Mid-ground - 300: BackgroundColorSet; - // The editor - // Foreground - 500: BackgroundColorSet; - // Hacks for elements on top of the midground - // Buttons in a panel, tab bar, or panel - on300: BackgroundColorSet; - // Hacks for elements on top of the editor - on500: BackgroundColorSet; - ok: BackgroundColorSet; - on500Ok: BackgroundColorSet; - error: BackgroundColorSet; - on500Error: BackgroundColorSet; - warning: BackgroundColorSet; - on500Warning: BackgroundColorSet; - info: BackgroundColorSet; - on500Info: BackgroundColorSet; - }; - borderColor: { - primary: string; - secondary: string; - muted: string; - active: string; - /** - * Used for rendering borders on top of media like avatars, images, video, etc. - */ - onMedia: string; - ok: string; - error: string; - warning: string; - info: string; - }; - textColor: { - primary: string; - secondary: string; - muted: string; - placeholder: string; - active: string; - feature: string; - ok: string; - error: string; - warning: string; - info: string; - onMedia: string; - }; - iconColor: { - primary: string; - secondary: string; - muted: string; - placeholder: string; - active: string; - feature: string; - ok: string; - error: string; - warning: string; - info: string; - }; - editor: { - background: string; - indent_guide: string; - indent_guide_active: string; - line: { - active: string; - highlighted: string; - }; - highlight: { - selection: string; - occurrence: string; - activeOccurrence: string; - matchingBracket: string; - match: string; - activeMatch: string; - related: string; - }; - gutter: { - primary: string; - active: string; - }; - }; - - syntax: Syntax; - - player: { - 1: Player; - 2: Player; - 3: Player; - 4: Player; - 5: Player; - 6: Player; - 7: Player; - 8: Player; - }; - shadow: string; - ramps: { [rampName: string]: Scale }; -} diff --git a/styles/src/themes/one-dark.ts b/styles/src/themes/one-dark.ts index 42a765e3e3a4fef6c393bb84901e4c6d40eb9955..a4326aa4c674a552328a7dd0af9533bb4e68d153 100644 --- a/styles/src/themes/one-dark.ts +++ b/styles/src/themes/one-dark.ts @@ -1,40 +1,69 @@ -import chroma from "chroma-js"; -import { Meta } from "./common/colorScheme"; -import { colorRamp, createColorScheme } from "./common/ramps"; +import chroma from "chroma-js" +import { Meta, ThemeSyntax } from "./common/colorScheme" +import { colorRamp, createColorScheme } from "./common/ramps" -const name = "One Dark"; +const name = "One Dark" -export const dark = createColorScheme(`${name}`, false, { - neutral: chroma - .scale([ - "#282c34", - "#353b45", - "#3e4451", - "#545862", - "#565c64", - "#abb2bf", - "#b6bdca", - "#c8ccd4", - ]) - .domain([0.05, 0.22, 0.25, 0.45, 0.62, 0.8, 0.9, 1]), +const color = { + white: "#ACB2BE", + grey: "#5D636F", + red: "#D07277", + orange: "#C0966B", + yellow: "#DFC184", + green: "#A1C181", + teal: "#6FB4C0", + blue: "#74ADE9", + purple: "#B478CF", +} + +const ramps = { + neutral: chroma + .scale([ + "#282c34", + "#353b45", + "#3e4451", + "#545862", + "#565c64", + "#abb2bf", + "#b6bdca", + "#c8ccd4", + ]) + .domain([0.05, 0.22, 0.25, 0.45, 0.62, 0.8, 0.9, 1]), + red: colorRamp(chroma(color.red)), + orange: colorRamp(chroma(color.orange)), + yellow: colorRamp(chroma(color.yellow)), + green: colorRamp(chroma(color.green)), + cyan: colorRamp(chroma(color.teal)), + blue: colorRamp(chroma(color.blue)), + violet: colorRamp(chroma(color.purple)), + magenta: colorRamp(chroma("#be5046")), +} + +const syntax: ThemeSyntax = { + primary: { color: color.white }, + comment: { color: color.grey }, + function: { color: color.blue }, + type: { color: color.teal }, + property: { color: color.red }, + number: { color: color.orange }, + string: { color: color.green }, + keyword: { color: color.purple }, + boolean: { color: color.orange }, + punctuation: { color: color.white }, + operator: { color: color.teal }, +} - red: colorRamp(chroma("#e06c75")), - orange: colorRamp(chroma("#d19a66")), - yellow: colorRamp(chroma("#e5c07b")), - green: colorRamp(chroma("#98c379")), - cyan: colorRamp(chroma("#56b6c2")), - blue: colorRamp(chroma("#61afef")), - violet: colorRamp(chroma("#c678dd")), - magenta: colorRamp(chroma("#be5046")), -}); +export const dark = createColorScheme(name, false, ramps, syntax) export const meta: Meta = { - name, - author: "simurai", - license: { - SPDX: "MIT", - https_url: "https://raw.githubusercontent.com/atom/atom/master/packages/one-light-ui/LICENSE.md", - license_checksum: "d5af8fc171f6f600c0ab4e7597dca398dda80dbe6821ce01cef78e859e7a00f8" - }, - url: "https://github.com/atom/atom/tree/master/packages/one-dark-ui" + name, + author: "simurai", + license: { + SPDX: "MIT", + https_url: + "https://raw.githubusercontent.com/atom/atom/master/packages/one-light-ui/LICENSE.md", + license_checksum: + "d5af8fc171f6f600c0ab4e7597dca398dda80dbe6821ce01cef78e859e7a00f8", + }, + url: "https://github.com/atom/atom/tree/master/packages/one-dark-ui", } diff --git a/styles/src/themes/one-light.ts b/styles/src/themes/one-light.ts index 50f99becdcd814038c2350a94b76852da5d493b3..91f66ec6eaaa7b04464917aa5cd0f23965be1dd8 100644 --- a/styles/src/themes/one-light.ts +++ b/styles/src/themes/one-light.ts @@ -1,39 +1,80 @@ -import chroma from "chroma-js"; -import { Meta } from "./common/colorScheme"; -import { colorRamp, createColorScheme } from "./common/ramps"; +import chroma from "chroma-js" +import { fontWeights } from "../common" +import { Meta, ThemeSyntax } from "./common/colorScheme" +import { colorRamp, createColorScheme } from "./common/ramps" -const name = "One Light"; +const name = "One Light" -export const light = createColorScheme(`${name}`, true, { - neutral: chroma.scale([ - "#090a0b", - "#202227", - "#383a42", - "#696c77", - "#a0a1a7", - "#e5e5e6", - "#f0f0f1", - "#fafafa", - ]) - .domain([0.05, 0.22, 0.25, 0.45, 0.62, 0.8, 0.9, 1]), +const color = { + black: "#383A41", + grey: "#A2A3A7", + red: "#D36050", + orange: "#AD6F26", + yellow: "#DFC184", + green: "#659F58", + teal: "#3982B7", + blue: "#5B79E3", + purple: "#A449AB", + magenta: "#994EA6", +} + +const ramps = { + neutral: chroma + .scale([ + "#383A41", + "#535456", + "#696c77", + "#9D9D9F", + "#A9A9A9", + "#DBDBDC", + "#EAEAEB", + "#FAFAFA", + ]) + .domain([0.05, 0.22, 0.25, 0.45, 0.62, 0.8, 0.9, 1]), + red: colorRamp(chroma(color.red)), + orange: colorRamp(chroma(color.orange)), + yellow: colorRamp(chroma(color.yellow)), + green: colorRamp(chroma(color.green)), + cyan: colorRamp(chroma(color.teal)), + blue: colorRamp(chroma(color.blue)), + violet: colorRamp(chroma(color.purple)), + magenta: colorRamp(chroma(color.magenta)), +} + +const syntax: ThemeSyntax = { + primary: { color: color.black }, + "variable.special": { color: color.orange }, + comment: { color: color.grey }, + punctuation: { color: color.black }, + keyword: { color: color.purple }, + function: { color: color.blue }, + type: { color: color.teal }, + variant: { color: color.blue }, + property: { color: color.red }, + enum: { color: color.red }, + operator: { color: color.teal }, + string: { color: color.green }, + number: { color: color.orange }, + boolean: { color: color.orange }, + title: { color: color.red, weight: fontWeights.normal }, + "emphasis.strong": { + color: color.orange, + }, + linkText: { color: color.blue }, + linkUri: { color: color.teal }, +} - red: colorRamp(chroma("#ca1243")), - orange: colorRamp(chroma("#d75f00")), - yellow: colorRamp(chroma("#c18401")), - green: colorRamp(chroma("#50a14f")), - cyan: colorRamp(chroma("#0184bc")), - blue: colorRamp(chroma("#4078f2")), - violet: colorRamp(chroma("#a626a4")), - magenta: colorRamp(chroma("#986801")), -}); +export const light = createColorScheme(name, true, ramps, syntax) export const meta: Meta = { - name, - author: "simurai", - license: { - SPDX: "MIT", - https_url: "https://raw.githubusercontent.com/atom/atom/master/packages/one-light-ui/LICENSE.md", - license_checksum: "d5af8fc171f6f600c0ab4e7597dca398dda80dbe6821ce01cef78e859e7a00f8" - }, - url: "https://github.com/atom/atom/tree/master/packages/one-light-ui" + name, + author: "simurai", + license: { + SPDX: "MIT", + https_url: + "https://raw.githubusercontent.com/atom/atom/master/packages/one-light-ui/LICENSE.md", + license_checksum: + "d5af8fc171f6f600c0ab4e7597dca398dda80dbe6821ce01cef78e859e7a00f8", + }, + url: "https://github.com/atom/atom/tree/master/packages/one-light-ui", } diff --git a/styles/src/themes/rose-pine-dawn.ts b/styles/src/themes/rose-pine-dawn.ts index b1744f9c2009eced02ab31c1f9324a57866f30df..8ba105395e029496b2b4731e8df855d8f053c8f0 100644 --- a/styles/src/themes/rose-pine-dawn.ts +++ b/styles/src/themes/rose-pine-dawn.ts @@ -1,41 +1,43 @@ -import chroma from "chroma-js"; -import { Meta } from "./common/colorScheme"; -import { colorRamp, createColorScheme } from "./common/ramps"; +import chroma from "chroma-js" +import { Meta } from "./common/colorScheme" +import { colorRamp, createColorScheme } from "./common/ramps" -const name = "Rosé Pine Dawn"; +const name = "Rosé Pine Dawn" const ramps = { - 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")), - green: colorRamp(chroma("#679967")), - cyan: colorRamp(chroma("#286983")), - blue: colorRamp(chroma("#56949F")), - violet: colorRamp(chroma("#907AA9")), - magenta: colorRamp(chroma("#79549F")), -}; + 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")), + green: colorRamp(chroma("#679967")), + cyan: colorRamp(chroma("#286983")), + blue: colorRamp(chroma("#56949F")), + violet: colorRamp(chroma("#907AA9")), + magenta: colorRamp(chroma("#79549F")), +} -export const light = createColorScheme(`${name}`, true, ramps); +export const light = createColorScheme(`${name}`, true, ramps) export const meta: Meta = { - name, - author: "edunfelt", - license: { - SPDX: "MIT", - https_url: "https://raw.githubusercontent.com/edunfelt/base16-rose-pine-scheme/main/LICENSE", - license_checksum: "6ca1b9da8c78c8441c5aa43d024a4e4a7bf59d1ecca1480196e94fda0f91ee4a" - }, - url: "https://github.com/edunfelt/base16-rose-pine-scheme" -} \ No newline at end of file + name, + author: "edunfelt", + license: { + SPDX: "MIT", + https_url: + "https://raw.githubusercontent.com/edunfelt/base16-rose-pine-scheme/main/LICENSE", + license_checksum: + "6ca1b9da8c78c8441c5aa43d024a4e4a7bf59d1ecca1480196e94fda0f91ee4a", + }, + url: "https://github.com/edunfelt/base16-rose-pine-scheme", +} diff --git a/styles/src/themes/rose-pine-moon.ts b/styles/src/themes/rose-pine-moon.ts index a4c1737c2b35ec8476366ce81675abda5afb9e8f..e13352a58a7b23ec05f0ce60b6f70c2039fa761c 100644 --- a/styles/src/themes/rose-pine-moon.ts +++ b/styles/src/themes/rose-pine-moon.ts @@ -1,41 +1,43 @@ -import chroma from "chroma-js"; -import { Meta } from "./common/colorScheme"; -import { colorRamp, createColorScheme } from "./common/ramps"; +import chroma from "chroma-js" +import { Meta } from "./common/colorScheme" +import { colorRamp, createColorScheme } from "./common/ramps" -const name = "Rosé Pine Moon"; +const name = "Rosé Pine Moon" const ramps = { - 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")), - green: colorRamp(chroma("#8DBD8D")), - cyan: colorRamp(chroma("#409BBE")), - blue: colorRamp(chroma("#9CCFD8")), - violet: colorRamp(chroma("#C4A7E7")), - magenta: colorRamp(chroma("#AB6FE9")), -}; + 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")), + green: colorRamp(chroma("#8DBD8D")), + cyan: colorRamp(chroma("#409BBE")), + blue: colorRamp(chroma("#9CCFD8")), + violet: colorRamp(chroma("#C4A7E7")), + magenta: colorRamp(chroma("#AB6FE9")), +} -export const dark = createColorScheme(`${name}`, false, ramps); +export const dark = createColorScheme(`${name}`, false, ramps) export const meta: Meta = { - name, - author: "edunfelt", - license: { - SPDX: "MIT", - https_url: "https://raw.githubusercontent.com/edunfelt/base16-rose-pine-scheme/main/LICENSE", - license_checksum: "6ca1b9da8c78c8441c5aa43d024a4e4a7bf59d1ecca1480196e94fda0f91ee4a" - }, - url: "https://github.com/edunfelt/base16-rose-pine-scheme" -} \ No newline at end of file + name, + author: "edunfelt", + license: { + SPDX: "MIT", + https_url: + "https://raw.githubusercontent.com/edunfelt/base16-rose-pine-scheme/main/LICENSE", + license_checksum: + "6ca1b9da8c78c8441c5aa43d024a4e4a7bf59d1ecca1480196e94fda0f91ee4a", + }, + url: "https://github.com/edunfelt/base16-rose-pine-scheme", +} diff --git a/styles/src/themes/rose-pine.ts b/styles/src/themes/rose-pine.ts index e3c115213b9cd6df2e4cc27327c7837ee654a232..bcbe94ce52e656b6dc9a150e50365e50198e6409 100644 --- a/styles/src/themes/rose-pine.ts +++ b/styles/src/themes/rose-pine.ts @@ -1,39 +1,41 @@ -import chroma from "chroma-js"; -import { Meta } from "./common/colorScheme"; -import { colorRamp, createColorScheme } from "./common/ramps"; +import chroma from "chroma-js" +import { Meta } from "./common/colorScheme" +import { colorRamp, createColorScheme } from "./common/ramps" -const name = "Rosé Pine"; +const name = "Rosé Pine" const ramps = { - neutral: chroma.scale([ - "#191724", - "#1f1d2e", - "#26233A", - "#3E3A53", - "#56526C", - "#6E6A86", - "#908CAA", - "#E0DEF4", - ]), - red: colorRamp(chroma("#EB6F92")), - orange: colorRamp(chroma("#EBBCBA")), - yellow: colorRamp(chroma("#F6C177")), - green: colorRamp(chroma("#8DBD8D")), - cyan: colorRamp(chroma("#409BBE")), - blue: colorRamp(chroma("#9CCFD8")), - violet: colorRamp(chroma("#C4A7E7")), - magenta: colorRamp(chroma("#AB6FE9")), -}; + neutral: chroma.scale([ + "#191724", + "#1f1d2e", + "#26233A", + "#3E3A53", + "#56526C", + "#6E6A86", + "#908CAA", + "#E0DEF4", + ]), + red: colorRamp(chroma("#EB6F92")), + orange: colorRamp(chroma("#EBBCBA")), + yellow: colorRamp(chroma("#F6C177")), + green: colorRamp(chroma("#8DBD8D")), + cyan: colorRamp(chroma("#409BBE")), + blue: colorRamp(chroma("#9CCFD8")), + violet: colorRamp(chroma("#C4A7E7")), + magenta: colorRamp(chroma("#AB6FE9")), +} -export const dark = createColorScheme(`${name}`, false, ramps); +export const dark = createColorScheme(`${name}`, false, ramps) export const meta: Meta = { - name, - author: "edunfelt", - license: { - SPDX: "MIT", - https_url: "https://raw.githubusercontent.com/edunfelt/base16-rose-pine-scheme/main/LICENSE", - license_checksum: "6ca1b9da8c78c8441c5aa43d024a4e4a7bf59d1ecca1480196e94fda0f91ee4a" - }, - url: "https://github.com/edunfelt/base16-rose-pine-scheme" -} \ No newline at end of file + name, + author: "edunfelt", + license: { + SPDX: "MIT", + https_url: + "https://raw.githubusercontent.com/edunfelt/base16-rose-pine-scheme/main/LICENSE", + license_checksum: + "6ca1b9da8c78c8441c5aa43d024a4e4a7bf59d1ecca1480196e94fda0f91ee4a", + }, + url: "https://github.com/edunfelt/base16-rose-pine-scheme", +} diff --git a/styles/src/themes/sandcastle.ts b/styles/src/themes/sandcastle.ts index 0e1328feabe269e48cb51e6d494f98dfa88f2812..47b81f29dfaf4dcca881a0fecba50e2d8060b851 100644 --- a/styles/src/themes/sandcastle.ts +++ b/styles/src/themes/sandcastle.ts @@ -1,40 +1,41 @@ -import chroma from "chroma-js"; -import { Meta } from "./common/colorScheme"; -import { colorRamp, createColorScheme } from "./common/ramps"; +import chroma from "chroma-js" +import { Meta } from "./common/colorScheme" +import { colorRamp, createColorScheme } from "./common/ramps" -const name = "Sandcastle"; +const name = "Sandcastle" const ramps = { - neutral: chroma.scale([ - "#282c34", - "#2c323b", - "#3e4451", - "#665c54", - "#928374", - "#a89984", - "#d5c4a1", - "#fdf4c1", - ]), - red: colorRamp(chroma("#B4637A")), - orange: colorRamp(chroma("#a07e3b")), - yellow: colorRamp(chroma("#a07e3b")), - green: colorRamp(chroma("#83a598")), - cyan: colorRamp(chroma("#83a598")), - blue: colorRamp(chroma("#528b8b")), - violet: colorRamp(chroma("#d75f5f")), - magenta: colorRamp(chroma("#a87322")), -}; + neutral: chroma.scale([ + "#282c34", + "#2c323b", + "#3e4451", + "#665c54", + "#928374", + "#a89984", + "#d5c4a1", + "#fdf4c1", + ]), + red: colorRamp(chroma("#B4637A")), + orange: colorRamp(chroma("#a07e3b")), + yellow: colorRamp(chroma("#a07e3b")), + green: colorRamp(chroma("#83a598")), + cyan: colorRamp(chroma("#83a598")), + blue: colorRamp(chroma("#528b8b")), + violet: colorRamp(chroma("#d75f5f")), + magenta: colorRamp(chroma("#a87322")), +} -export const dark = createColorScheme(`${name}`, false, ramps); +export const dark = createColorScheme(`${name}`, false, ramps) export const meta: Meta = { - name, - author: "gessig", - license: { - SPDX: "MIT", - https_url: "https://raw.githubusercontent.com/gessig/base16-sandcastle-scheme/master/LICENSE", - license_checksum: "8399d44b4d935b60be9fee0a76d7cc9a817b4f3f11574c9d6d1e8fd57e72ffdc" - }, - url: "https://github.com/gessig/base16-sandcastle-scheme" + name, + author: "gessig", + license: { + SPDX: "MIT", + https_url: + "https://raw.githubusercontent.com/gessig/base16-sandcastle-scheme/master/LICENSE", + license_checksum: + "8399d44b4d935b60be9fee0a76d7cc9a817b4f3f11574c9d6d1e8fd57e72ffdc", + }, + url: "https://github.com/gessig/base16-sandcastle-scheme", } - diff --git a/styles/src/themes/solarized.ts b/styles/src/themes/solarized.ts index 98f9339d6ed9c02a4bd8820bc0f2057f525a8f10..51baeed56403fdf08df3105075679e24f9902f4d 100644 --- a/styles/src/themes/solarized.ts +++ b/styles/src/themes/solarized.ts @@ -1,43 +1,44 @@ -import chroma from "chroma-js"; -import { Meta as Metadata } from "./common/colorScheme"; -import { colorRamp, createColorScheme } from "./common/ramps"; +import chroma from "chroma-js" +import { Meta as Metadata } from "./common/colorScheme" +import { colorRamp, createColorScheme } from "./common/ramps" -const name = "Solarized"; +const name = "Solarized" const ramps = { - 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")), - green: colorRamp(chroma("#859900")), - cyan: colorRamp(chroma("#2aa198")), - blue: colorRamp(chroma("#268bd2")), - violet: colorRamp(chroma("#6c71c4")), - magenta: colorRamp(chroma("#d33682")), -}; + 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")), + green: colorRamp(chroma("#859900")), + cyan: colorRamp(chroma("#2aa198")), + blue: colorRamp(chroma("#268bd2")), + violet: colorRamp(chroma("#6c71c4")), + magenta: colorRamp(chroma("#d33682")), +} -export const dark = createColorScheme(`${name} Dark`, false, ramps); -export const light = createColorScheme(`${name} Light`, true, ramps); +export const dark = createColorScheme(`${name} Dark`, false, ramps) +export const light = createColorScheme(`${name} Light`, true, ramps) export const meta: Metadata = { - name, - author: "Ethan Schoonover", - license: { - SPDX: "MIT", - https_url: "https://raw.githubusercontent.com/altercation/solarized/master/LICENSE", - license_checksum: "494aefdabf86acce06bd63001ad8aedad4ee38da23509d3f917d95aa3368b9a6" - }, - url: "https://github.com/altercation/solarized" + name, + author: "Ethan Schoonover", + license: { + SPDX: "MIT", + https_url: + "https://raw.githubusercontent.com/altercation/solarized/master/LICENSE", + license_checksum: + "494aefdabf86acce06bd63001ad8aedad4ee38da23509d3f917d95aa3368b9a6", + }, + url: "https://github.com/altercation/solarized", } - diff --git a/styles/src/themes/staff/abruzzo.ts b/styles/src/themes/staff/abruzzo.ts index f3c08f142685b12622f50b0070a28c46d174d0f3..7a1f0f956aaab8fbebb05dbc1d3c39bc3242e9d7 100644 --- a/styles/src/themes/staff/abruzzo.ts +++ b/styles/src/themes/staff/abruzzo.ts @@ -1,31 +1,31 @@ -import chroma from "chroma-js"; -import { colorRamp, createColorScheme } from "../common/ramps"; +import chroma from "chroma-js" +import { colorRamp, createColorScheme } from "../common/ramps" -const name = "Abruzzo"; -const author = "slightknack "; -const url = "https://github.com/slightknack"; +const name = "Abruzzo" +const author = "slightknack " +const url = "https://github.com/slightknack" const license = { - type: "", - url: "" + type: "", + url: "", } export const dark = createColorScheme(`${name}`, false, { - 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")), -}); + 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")), +}) diff --git a/styles/src/themes/staff/atelier-dune.ts b/styles/src/themes/staff/atelier-dune.ts index 9879fe4b583b17d71b096b186a4ce3b35aa1e608..bd39098dc3575ce8a0a4c515e6a33d151a521658 100644 --- a/styles/src/themes/staff/atelier-dune.ts +++ b/styles/src/themes/staff/atelier-dune.ts @@ -1,34 +1,35 @@ -import chroma from "chroma-js"; -import { colorRamp, createColorScheme } from "../common/ramps"; +import chroma from "chroma-js" +import { colorRamp, createColorScheme } from "../common/ramps" -const name = "Atelier Dune"; -const author = "atelierbram"; -const url = "https://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune/"; +const name = "Atelier Dune" +const author = "atelierbram" +const url = + "https://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune/" const license = { - type: "MIT", - url: "https://github.com/atelierbram/syntax-highlighting/blob/master/LICENSE", -}; + type: "MIT", + url: "https://github.com/atelierbram/syntax-highlighting/blob/master/LICENSE", +} const ramps = { - neutral: chroma.scale([ - "#20201d", - "#292824", - "#6e6b5e", - "#7d7a68", - "#999580", - "#a6a28c", - "#e8e4cf", - "#fefbec", - ]), - red: colorRamp(chroma("#d73737")), - orange: colorRamp(chroma("#b65611")), - yellow: colorRamp(chroma("#ae9513")), - green: colorRamp(chroma("#60ac39")), - cyan: colorRamp(chroma("#1fad83")), - blue: colorRamp(chroma("#6684e1")), - violet: colorRamp(chroma("#b854d4")), - magenta: colorRamp(chroma("#d43552")), -}; + neutral: chroma.scale([ + "#20201d", + "#292824", + "#6e6b5e", + "#7d7a68", + "#999580", + "#a6a28c", + "#e8e4cf", + "#fefbec", + ]), + red: colorRamp(chroma("#d73737")), + orange: colorRamp(chroma("#b65611")), + yellow: colorRamp(chroma("#ae9513")), + green: colorRamp(chroma("#60ac39")), + cyan: colorRamp(chroma("#1fad83")), + blue: colorRamp(chroma("#6684e1")), + violet: colorRamp(chroma("#b854d4")), + magenta: colorRamp(chroma("#d43552")), +} -export const dark = createColorScheme(`${name} Dark`, false, ramps); -export const light = createColorScheme(`${name} Light`, true, ramps); +export const dark = createColorScheme(`${name} Dark`, false, ramps) +export const light = createColorScheme(`${name} Light`, true, ramps) diff --git a/styles/src/themes/staff/atelier-heath.ts b/styles/src/themes/staff/atelier-heath.ts index e73e919dad4055409073dcf06ba44823cb201671..cd4eb2a1ac597c127b384c6f83f478c78737ed4d 100644 --- a/styles/src/themes/staff/atelier-heath.ts +++ b/styles/src/themes/staff/atelier-heath.ts @@ -1,53 +1,54 @@ -import chroma from "chroma-js"; -import { colorRamp, createColorScheme } from "../common/ramps"; +import chroma from "chroma-js" +import { colorRamp, createColorScheme } from "../common/ramps" -const name = "Atelier Heath"; -const author = "atelierbram"; -const url = "https://atelierbram.github.io/syntax-highlighting/atelier-schemes/heath/"; +const name = "Atelier Heath" +const author = "atelierbram" +const url = + "https://atelierbram.github.io/syntax-highlighting/atelier-schemes/heath/" const license = { - type: "MIT", - url: "https://github.com/atelierbram/syntax-highlighting/blob/master/LICENSE", -}; + type: "MIT", + url: "https://github.com/atelierbram/syntax-highlighting/blob/master/LICENSE", +} // `name-[light|dark]`, isLight, color ramps export const dark = createColorScheme(`${name} Dark`, false, { - neutral: chroma.scale([ - "#1b181b", - "#292329", - "#695d69", - "#776977", - "#9e8f9e", - "#ab9bab", - "#d8cad8", - "#f7f3f7", - ]), - red: colorRamp(chroma("#ca402b")), - orange: colorRamp(chroma("#a65926")), - yellow: colorRamp(chroma("#bb8a35")), - green: colorRamp(chroma("#918b3b")), - cyan: colorRamp(chroma("#159393")), - blue: colorRamp(chroma("#516aec")), - violet: colorRamp(chroma("#7b59c0")), - magenta: colorRamp(chroma("#cc33cc")), -}); + neutral: chroma.scale([ + "#1b181b", + "#292329", + "#695d69", + "#776977", + "#9e8f9e", + "#ab9bab", + "#d8cad8", + "#f7f3f7", + ]), + red: colorRamp(chroma("#ca402b")), + orange: colorRamp(chroma("#a65926")), + yellow: colorRamp(chroma("#bb8a35")), + green: colorRamp(chroma("#918b3b")), + cyan: colorRamp(chroma("#159393")), + blue: colorRamp(chroma("#516aec")), + violet: colorRamp(chroma("#7b59c0")), + magenta: colorRamp(chroma("#cc33cc")), +}) export const light = createColorScheme(`${name} Light`, true, { - neutral: chroma.scale([ - "#161b1d", - "#1f292e", - "#516d7b", - "#5a7b8c", - "#7195a8", - "#7ea2b4", - "#c1e4f6", - "#ebf8ff", - ]), - red: colorRamp(chroma("#d22d72")), - orange: colorRamp(chroma("#935c25")), - yellow: colorRamp(chroma("#8a8a0f")), - green: colorRamp(chroma("#568c3b")), - cyan: colorRamp(chroma("#2d8f6f")), - blue: colorRamp(chroma("#257fad")), - violet: colorRamp(chroma("#6b6bb8")), - magenta: colorRamp(chroma("#b72dd2")), -}); + neutral: chroma.scale([ + "#161b1d", + "#1f292e", + "#516d7b", + "#5a7b8c", + "#7195a8", + "#7ea2b4", + "#c1e4f6", + "#ebf8ff", + ]), + red: colorRamp(chroma("#d22d72")), + orange: colorRamp(chroma("#935c25")), + yellow: colorRamp(chroma("#8a8a0f")), + green: colorRamp(chroma("#568c3b")), + cyan: colorRamp(chroma("#2d8f6f")), + blue: colorRamp(chroma("#257fad")), + violet: colorRamp(chroma("#6b6bb8")), + magenta: colorRamp(chroma("#b72dd2")), +}) diff --git a/styles/src/themes/staff/atelier-seaside.ts b/styles/src/themes/staff/atelier-seaside.ts index 74c8112f77bbdda1a47e258e3e8a2b457e41efe8..13286e77f929b11ebce5c0a5405f505d9ee34669 100644 --- a/styles/src/themes/staff/atelier-seaside.ts +++ b/styles/src/themes/staff/atelier-seaside.ts @@ -1,34 +1,35 @@ -import chroma from "chroma-js"; -import { colorRamp, createColorScheme } from "../common/ramps"; +import chroma from "chroma-js" +import { colorRamp, createColorScheme } from "../common/ramps" -const name = "Atelier Seaside"; -const author = "atelierbram"; -const url = "https://atelierbram.github.io/syntax-highlighting/atelier-schemes/seaside/"; +const name = "Atelier Seaside" +const author = "atelierbram" +const url = + "https://atelierbram.github.io/syntax-highlighting/atelier-schemes/seaside/" const license = { - type: "MIT", - url: "https://github.com/atelierbram/syntax-highlighting/blob/master/LICENSE", -}; + type: "MIT", + url: "https://github.com/atelierbram/syntax-highlighting/blob/master/LICENSE", +} const ramps = { - neutral: chroma.scale([ - "#131513", - "#242924", - "#5e6e5e", - "#687d68", - "#809980", - "#8ca68c", - "#cfe8cf", - "#f4fbf4", - ]), - red: colorRamp(chroma("#e6193c")), - orange: colorRamp(chroma("#87711d")), - yellow: colorRamp(chroma("#98981b")), - green: colorRamp(chroma("#29a329")), - cyan: colorRamp(chroma("#1999b3")), - blue: colorRamp(chroma("#3d62f5")), - violet: colorRamp(chroma("#ad2bee")), - magenta: colorRamp(chroma("#e619c3")), -}; + neutral: chroma.scale([ + "#131513", + "#242924", + "#5e6e5e", + "#687d68", + "#809980", + "#8ca68c", + "#cfe8cf", + "#f4fbf4", + ]), + red: colorRamp(chroma("#e6193c")), + orange: colorRamp(chroma("#87711d")), + yellow: colorRamp(chroma("#98981b")), + green: colorRamp(chroma("#29a329")), + cyan: colorRamp(chroma("#1999b3")), + blue: colorRamp(chroma("#3d62f5")), + violet: colorRamp(chroma("#ad2bee")), + magenta: colorRamp(chroma("#e619c3")), +} -export const dark = createColorScheme(`${name} Dark`, false, ramps); -export const light = createColorScheme(`${name} Light`, true, ramps); +export const dark = createColorScheme(`${name} Dark`, false, ramps) +export const light = createColorScheme(`${name} Light`, true, ramps) diff --git a/styles/src/themes/staff/ayu-mirage.ts b/styles/src/themes/staff/ayu-mirage.ts index 9294a9af646c94340730dd9ad58fa0ab1520d456..5b832699b46b984d9fd0fdaff223073613095aed 100644 --- a/styles/src/themes/staff/ayu-mirage.ts +++ b/styles/src/themes/staff/ayu-mirage.ts @@ -1,31 +1,31 @@ -import chroma from "chroma-js"; -import { colorRamp, createColorScheme } from "../common/ramps"; +import chroma from "chroma-js" +import { colorRamp, createColorScheme } from "../common/ramps" -const name = "Ayu"; -const author = "Konstantin Pschera "; -const url = "https://github.com/ayu-theme/ayu-colors"; +const name = "Ayu" +const author = "Konstantin Pschera " +const url = "https://github.com/ayu-theme/ayu-colors" const license = { - type: "MIT", - url: "https://github.com/ayu-theme/ayu-colors/blob/master/license" + type: "MIT", + url: "https://github.com/ayu-theme/ayu-colors/blob/master/license", } export const dark = createColorScheme(`${name} Mirage`, false, { - neutral: chroma.scale([ - "#171B24", - "#1F2430", - "#242936", - "#707A8C", - "#8A9199", - "#CCCAC2", - "#D9D7CE", - "#F3F4F5", - ]), - red: colorRamp(chroma("#F28779")), - orange: colorRamp(chroma("#FFAD66")), - yellow: colorRamp(chroma("#FFD173")), - green: colorRamp(chroma("#D5FF80")), - cyan: colorRamp(chroma("#95E6CB")), - blue: colorRamp(chroma("#5CCFE6")), - violet: colorRamp(chroma("#D4BFFF")), - magenta: colorRamp(chroma("#F29E74")), -}); + neutral: chroma.scale([ + "#171B24", + "#1F2430", + "#242936", + "#707A8C", + "#8A9199", + "#CCCAC2", + "#D9D7CE", + "#F3F4F5", + ]), + red: colorRamp(chroma("#F28779")), + orange: colorRamp(chroma("#FFAD66")), + yellow: colorRamp(chroma("#FFD173")), + green: colorRamp(chroma("#D5FF80")), + cyan: colorRamp(chroma("#95E6CB")), + blue: colorRamp(chroma("#5CCFE6")), + violet: colorRamp(chroma("#D4BFFF")), + magenta: colorRamp(chroma("#F29E74")), +}) diff --git a/styles/src/themes/staff/ayu.ts b/styles/src/themes/staff/ayu.ts index c5a526d8ceed79a3e4894c55f426af4ad9c2e0f3..24fcdb951b07aa3e7346c28566b26a549e388928 100644 --- a/styles/src/themes/staff/ayu.ts +++ b/styles/src/themes/staff/ayu.ts @@ -1,52 +1,52 @@ -import chroma from "chroma-js"; -import { colorRamp, createColorScheme } from "../common/ramps"; +import chroma from "chroma-js" +import { colorRamp, createColorScheme } from "../common/ramps" -const name = "Ayu"; -const author = "Konstantin Pschera "; -const url = "https://github.com/ayu-theme/ayu-colors"; +const name = "Ayu" +const author = "Konstantin Pschera " +const url = "https://github.com/ayu-theme/ayu-colors" const license = { - type: "MIT", - url: "https://github.com/ayu-theme/ayu-colors/blob/master/license" + type: "MIT", + url: "https://github.com/ayu-theme/ayu-colors/blob/master/license", } export const dark = createColorScheme(`${name} Dark`, false, { - neutral: chroma.scale([ - "#0F1419", - "#131721", - "#272D38", - "#3E4B59", - "#BFBDB6", - "#E6E1CF", - "#E6E1CF", - "#F3F4F5", - ]), - red: colorRamp(chroma("#F07178")), - orange: colorRamp(chroma("#FF8F40")), - yellow: colorRamp(chroma("#FFB454")), - green: colorRamp(chroma("#B8CC52")), - cyan: colorRamp(chroma("#95E6CB")), - blue: colorRamp(chroma("#59C2FF")), - violet: colorRamp(chroma("#D2A6FF")), - magenta: colorRamp(chroma("#E6B673")), -}); + neutral: chroma.scale([ + "#0F1419", + "#131721", + "#272D38", + "#3E4B59", + "#BFBDB6", + "#E6E1CF", + "#E6E1CF", + "#F3F4F5", + ]), + red: colorRamp(chroma("#F07178")), + orange: colorRamp(chroma("#FF8F40")), + yellow: colorRamp(chroma("#FFB454")), + green: colorRamp(chroma("#B8CC52")), + cyan: colorRamp(chroma("#95E6CB")), + blue: colorRamp(chroma("#59C2FF")), + violet: colorRamp(chroma("#D2A6FF")), + magenta: colorRamp(chroma("#E6B673")), +}) export const light = createColorScheme(`${name} Light`, true, { - neutral: chroma.scale([ - "#1A1F29", - "#242936", - "#5C6773", - "#828C99", - "#ABB0B6", - "#F8F9FA", - "#F3F4F5", - "#FAFAFA", - ]), - red: colorRamp(chroma("#F07178")), - orange: colorRamp(chroma("#FA8D3E")), - yellow: colorRamp(chroma("#F2AE49")), - green: colorRamp(chroma("#86B300")), - cyan: colorRamp(chroma("#4CBF99")), - blue: colorRamp(chroma("#36A3D9")), - violet: colorRamp(chroma("#A37ACC")), - magenta: colorRamp(chroma("#E6BA7E")), -}); + neutral: chroma.scale([ + "#1A1F29", + "#242936", + "#5C6773", + "#828C99", + "#ABB0B6", + "#F8F9FA", + "#F3F4F5", + "#FAFAFA", + ]), + red: colorRamp(chroma("#F07178")), + orange: colorRamp(chroma("#FA8D3E")), + yellow: colorRamp(chroma("#F2AE49")), + green: colorRamp(chroma("#86B300")), + cyan: colorRamp(chroma("#4CBF99")), + blue: colorRamp(chroma("#36A3D9")), + violet: colorRamp(chroma("#A37ACC")), + magenta: colorRamp(chroma("#E6BA7E")), +}) diff --git a/styles/src/themes/staff/brushtrees.ts b/styles/src/themes/staff/brushtrees.ts index f14f1abe8c36ce3fea402052cc7e05ea57e75666..a17cf92acb563b4ceea672f79436566062c86db8 100644 --- a/styles/src/themes/staff/brushtrees.ts +++ b/styles/src/themes/staff/brushtrees.ts @@ -1,73 +1,73 @@ -import chroma from "chroma-js"; -import { colorRamp, createColorScheme } from "../common/ramps"; +import chroma from "chroma-js" +import { colorRamp, createColorScheme } from "../common/ramps" -const name = "Brush Trees"; -const author = "Abraham White "; -const url = "https://github.com/WhiteAbeLincoln/base16-brushtrees-scheme"; +const name = "Brush Trees" +const author = "Abraham White " +const url = "https://github.com/WhiteAbeLincoln/base16-brushtrees-scheme" const license = { - type: "MIT", - url: "https://github.com/WhiteAbeLincoln/base16-brushtrees-scheme/blob/master/LICENSE" + type: "MIT", + url: "https://github.com/WhiteAbeLincoln/base16-brushtrees-scheme/blob/master/LICENSE", } export const dark = createColorScheme(`${name} Dark`, false, { - 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")), -}); + 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 mirage = createColorScheme(`${name} Mirage`, false, { - neutral: chroma.scale([ - "#485867", - "#5A6D7A", - "#6D828E", - "#8299A1", - "#98AFB5", - "#B0C5C8", - "#C9DBDC", - "#E3EFEF", - ]), - red: colorRamp(chroma("#F28779")), - orange: colorRamp(chroma("#FFAD66")), - yellow: colorRamp(chroma("#FFD173")), - green: colorRamp(chroma("#D5FF80")), - cyan: colorRamp(chroma("#95E6CB")), - blue: colorRamp(chroma("#5CCFE6")), - violet: colorRamp(chroma("#D4BFFF")), - magenta: colorRamp(chroma("#F29E74")), -}); + neutral: chroma.scale([ + "#485867", + "#5A6D7A", + "#6D828E", + "#8299A1", + "#98AFB5", + "#B0C5C8", + "#C9DBDC", + "#E3EFEF", + ]), + red: colorRamp(chroma("#F28779")), + orange: colorRamp(chroma("#FFAD66")), + yellow: colorRamp(chroma("#FFD173")), + green: colorRamp(chroma("#D5FF80")), + cyan: colorRamp(chroma("#95E6CB")), + blue: colorRamp(chroma("#5CCFE6")), + violet: colorRamp(chroma("#D4BFFF")), + magenta: colorRamp(chroma("#F29E74")), +}) export const light = createColorScheme(`${name} Light`, true, { - neutral: chroma.scale([ - "#1A1F29", - "#242936", - "#5C6773", - "#828C99", - "#ABB0B6", - "#F8F9FA", - "#F3F4F5", - "#FAFAFA", - ]), - 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")), -}); + neutral: chroma.scale([ + "#1A1F29", + "#242936", + "#5C6773", + "#828C99", + "#ABB0B6", + "#F8F9FA", + "#F3F4F5", + "#FAFAFA", + ]), + 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")), +}) diff --git a/styles/src/themes/staff/dracula.ts b/styles/src/themes/staff/dracula.ts index 0571574049d09121e2ec579e9e42cd8ca7129314..855fc20724584cf7dd2ad515057290e868ee5c43 100644 --- a/styles/src/themes/staff/dracula.ts +++ b/styles/src/themes/staff/dracula.ts @@ -1,31 +1,31 @@ -import chroma from "chroma-js"; -import { colorRamp, createColorScheme } from "../common/ramps"; +import chroma from "chroma-js" +import { colorRamp, createColorScheme } from "../common/ramps" -const name = "Dracula"; -const author = "zenorocha"; -const url = "https://github.com/dracula/dracula-theme"; +const name = "Dracula" +const author = "zenorocha" +const url = "https://github.com/dracula/dracula-theme" const license = { - type: "MIT", - url: "https://github.com/dracula/dracula-theme/blob/master/LICENSE", -}; + type: "MIT", + url: "https://github.com/dracula/dracula-theme/blob/master/LICENSE", +} export const dark = createColorScheme(`${name}`, false, { - neutral: chroma.scale([ - "#282A36", - "#3a3c4e", - "#4d4f68", - "#626483", - "#62d6e8", - "#e9e9f4", - "#f1f2f8", - "#f8f8f2", - ]), - red: colorRamp(chroma("#ff5555")), - orange: colorRamp(chroma("#ffb86c")), - yellow: colorRamp(chroma("#f1fa8c")), - green: colorRamp(chroma("#50fa7b")), - cyan: colorRamp(chroma("#8be9fd")), - blue: colorRamp(chroma("#6272a4")), - violet: colorRamp(chroma("#bd93f9")), - magenta: colorRamp(chroma("#00f769")), -}); + neutral: chroma.scale([ + "#282A36", + "#3a3c4e", + "#4d4f68", + "#626483", + "#62d6e8", + "#e9e9f4", + "#f1f2f8", + "#f8f8f2", + ]), + red: colorRamp(chroma("#ff5555")), + orange: colorRamp(chroma("#ffb86c")), + yellow: colorRamp(chroma("#f1fa8c")), + green: colorRamp(chroma("#50fa7b")), + cyan: colorRamp(chroma("#8be9fd")), + blue: colorRamp(chroma("#6272a4")), + violet: colorRamp(chroma("#bd93f9")), + magenta: colorRamp(chroma("#00f769")), +}) diff --git a/styles/src/themes/staff/gruvbox-medium.ts b/styles/src/themes/staff/gruvbox-medium.ts index 26707f627f9b27b8dc76ca3d5ca8d5a0c35d18fe..9e259bebc6c47c2cba0095587da2654fec46e51e 100644 --- a/styles/src/themes/staff/gruvbox-medium.ts +++ b/styles/src/themes/staff/gruvbox-medium.ts @@ -1,138 +1,138 @@ -import chroma from "chroma-js"; -import { colorRamp, createColorScheme } from "../common/ramps"; +import chroma from "chroma-js" +import { colorRamp, createColorScheme } from "../common/ramps" -const name = "Gruvbox"; -const author = "Dawid Kurek (dawikur@gmail.com)"; -const url = "https://github.com/morhetz/gruvbox"; +const name = "Gruvbox" +const author = "Dawid Kurek (dawikur@gmail.com)" +const url = "https://github.com/morhetz/gruvbox" const license = { - type: "MIT/X11", - url: "https://en.wikipedia.org/wiki/MIT_License", -}; + type: "MIT/X11", + url: "https://en.wikipedia.org/wiki/MIT_License", +} export const dark = createColorScheme(`${name} Dark Medium`, false, { - neutral: chroma.scale([ - "#282828", - "#3c3836", - "#504945", - "#665c54", - "#7C6F64", - "#928374", - "#A89984", - "#BDAE93", - "#D5C4A1", - "#EBDBB2", - "#FBF1C7", - ]), - red: chroma.scale([ - "#4D150F", - "#7D241A", - "#A31C17", - "#CC241D", - "#C83A29", - "#FB4934", - "#F06D61", - "#E6928E", - "#FFFFFF", - ]), - orange: chroma.scale([ - "#462307", - "#7F400C", - "#AB4A0B", - "#D65D0E", - "#CB6614", - "#FE8019", - "#F49750", - "#EBAE87", - "#FFFFFF", - ]), - yellow: chroma.scale([ - "#3D2C05", - "#7D5E17", - "#AC7A1A", - "#D79921", - "#E8AB28", - "#FABD2F", - "#F2C45F", - "#EBCC90", - "#FFFFFF", - ]), - green: chroma.scale([ - "#32330A", - "#5C5D13", - "#797814", - "#98971A", - "#93951E", - "#B8BB26", - "#C2C359", - "#CCCB8D", - "#FFFFFF", - ]), - cyan: chroma.scale([ - "#283D20", - "#47603E", - "#537D54", - "#689D6A", - "#719963", - "#8EC07C", - "#A1C798", - "#B4CEB5", - "#FFFFFF", - ]), - blue: chroma.scale([ - "#103738", - "#214C4D", - "#376A6C", - "#458588", - "#688479", - "#83A598", - "#92B3AE", - "#A2C2C4", - "#FFFFFF", - ]), - violet: chroma.scale([ - "#392228", - "#69434D", - "#8D4E6B", - "#B16286", - "#A86B7C", - "#D3869B", - "#D59BAF", - "#D8B1C3", - "#FFFFFF", - ]), - magenta: chroma.scale([ - "#48402C", - "#756D59", - "#867A69", - "#A89984", - "#BCAF8E", - "#EBDBB2", - "#DFD3BA", - "#D4CCC2", - "#FFFFFF", - ]), -}); + neutral: chroma.scale([ + "#282828", + "#3c3836", + "#504945", + "#665c54", + "#7C6F64", + "#928374", + "#A89984", + "#BDAE93", + "#D5C4A1", + "#EBDBB2", + "#FBF1C7", + ]), + red: chroma.scale([ + "#4D150F", + "#7D241A", + "#A31C17", + "#CC241D", + "#C83A29", + "#FB4934", + "#F06D61", + "#E6928E", + "#FFFFFF", + ]), + orange: chroma.scale([ + "#462307", + "#7F400C", + "#AB4A0B", + "#D65D0E", + "#CB6614", + "#FE8019", + "#F49750", + "#EBAE87", + "#FFFFFF", + ]), + yellow: chroma.scale([ + "#3D2C05", + "#7D5E17", + "#AC7A1A", + "#D79921", + "#E8AB28", + "#FABD2F", + "#F2C45F", + "#EBCC90", + "#FFFFFF", + ]), + green: chroma.scale([ + "#32330A", + "#5C5D13", + "#797814", + "#98971A", + "#93951E", + "#B8BB26", + "#C2C359", + "#CCCB8D", + "#FFFFFF", + ]), + cyan: chroma.scale([ + "#283D20", + "#47603E", + "#537D54", + "#689D6A", + "#719963", + "#8EC07C", + "#A1C798", + "#B4CEB5", + "#FFFFFF", + ]), + blue: chroma.scale([ + "#103738", + "#214C4D", + "#376A6C", + "#458588", + "#688479", + "#83A598", + "#92B3AE", + "#A2C2C4", + "#FFFFFF", + ]), + violet: chroma.scale([ + "#392228", + "#69434D", + "#8D4E6B", + "#B16286", + "#A86B7C", + "#D3869B", + "#D59BAF", + "#D8B1C3", + "#FFFFFF", + ]), + magenta: chroma.scale([ + "#48402C", + "#756D59", + "#867A69", + "#A89984", + "#BCAF8E", + "#EBDBB2", + "#DFD3BA", + "#D4CCC2", + "#FFFFFF", + ]), +}) export const light = createColorScheme(`${name} Light Medium`, true, { - neutral: chroma.scale([ - "#282828", - "#3c3836", - "#504945", - "#665c54", - "#7C6F64", - "#928374", - "#A89984", - "#BDAE93", - "#D5C4A1", - "#EBDBB2", - "#FBF1C7", - ]), - red: colorRamp(chroma("#9d0006")), - orange: colorRamp(chroma("#af3a03")), - yellow: colorRamp(chroma("#b57614")), - green: colorRamp(chroma("#79740e")), - cyan: colorRamp(chroma("#427b58")), - blue: colorRamp(chroma("#076678")), - violet: colorRamp(chroma("#8f3f71")), - magenta: colorRamp(chroma("#d65d0e")), -}); + neutral: chroma.scale([ + "#282828", + "#3c3836", + "#504945", + "#665c54", + "#7C6F64", + "#928374", + "#A89984", + "#BDAE93", + "#D5C4A1", + "#EBDBB2", + "#FBF1C7", + ]), + red: colorRamp(chroma("#9d0006")), + orange: colorRamp(chroma("#af3a03")), + yellow: colorRamp(chroma("#b57614")), + green: colorRamp(chroma("#79740e")), + cyan: colorRamp(chroma("#427b58")), + blue: colorRamp(chroma("#076678")), + violet: colorRamp(chroma("#8f3f71")), + magenta: colorRamp(chroma("#d65d0e")), +}) diff --git a/styles/src/themes/staff/monokai.ts b/styles/src/themes/staff/monokai.ts index 0a7ee275bbd11052a2d83f98b2dbf1bf8a6de7f1..25ae88a269973dcc03c9bba4c3a675ff3f8eb474 100644 --- a/styles/src/themes/staff/monokai.ts +++ b/styles/src/themes/staff/monokai.ts @@ -1,32 +1,32 @@ -import chroma from "chroma-js"; -import { colorRamp, createColorScheme } from "../common/ramps"; +import chroma from "chroma-js" +import { colorRamp, createColorScheme } from "../common/ramps" -const name = "Monokai"; -const author = "Wimer Hazenberg (http://www.monokai.nl)"; -const url = "https://base16.netlify.app/previews/base16-monokai.html"; +const name = "Monokai" +const author = "Wimer Hazenberg (http://www.monokai.nl)" +const url = "https://base16.netlify.app/previews/base16-monokai.html" const license = { - type: "?", - url: "?", -}; + type: "?", + url: "?", +} // `name-[light|dark]`, isLight, color ramps export const dark = createColorScheme(`${name}`, false, { - neutral: chroma.scale([ - "#272822", - "#383830", - "#49483e", - "#75715e", - "#a59f85", - "#f8f8f2", - "#f5f4f1", - "#f9f8f5", - ]), - red: colorRamp(chroma("#f92672")), - orange: colorRamp(chroma("#fd971f")), - yellow: colorRamp(chroma("#f4bf75")), - green: colorRamp(chroma("#a6e22e")), - cyan: colorRamp(chroma("#a1efe4")), - blue: colorRamp(chroma("#66d9ef")), - violet: colorRamp(chroma("#ae81ff")), - magenta: colorRamp(chroma("#cc6633")), -}); + neutral: chroma.scale([ + "#272822", + "#383830", + "#49483e", + "#75715e", + "#a59f85", + "#f8f8f2", + "#f5f4f1", + "#f9f8f5", + ]), + red: colorRamp(chroma("#f92672")), + orange: colorRamp(chroma("#fd971f")), + yellow: colorRamp(chroma("#f4bf75")), + green: colorRamp(chroma("#a6e22e")), + cyan: colorRamp(chroma("#a1efe4")), + blue: colorRamp(chroma("#66d9ef")), + violet: colorRamp(chroma("#ae81ff")), + magenta: colorRamp(chroma("#cc6633")), +}) diff --git a/styles/src/themes/staff/nord.ts b/styles/src/themes/staff/nord.ts index 5e303fcd471be71509240cd4f7a71ee618b03a4d..50a47700d929967c67869542f7a0fe0047e6d60c 100644 --- a/styles/src/themes/staff/nord.ts +++ b/styles/src/themes/staff/nord.ts @@ -1,32 +1,32 @@ -import chroma from "chroma-js"; -import { colorRamp, createColorScheme } from "../common/ramps"; +import chroma from "chroma-js" +import { colorRamp, createColorScheme } from "../common/ramps" -const name = "Nord"; -const author = "arcticicestudio"; -const url = "https://www.nordtheme.com/"; +const name = "Nord" +const author = "arcticicestudio" +const url = "https://www.nordtheme.com/" const license = { - type: "MIT", - url: "https://github.com/arcticicestudio/nord/blob/develop/LICENSE.md", -}; + type: "MIT", + url: "https://github.com/arcticicestudio/nord/blob/develop/LICENSE.md", +} // `name-[light|dark]`, isLight, color ramps export const dark = createColorScheme(`${name}`, false, { - neutral: chroma.scale([ - "#2E3440", - "#3B4252", - "#434C5E", - "#4C566A", - "#D8DEE9", - "#E5E9F0", - "#ECEFF4", - "#8FBCBB", - ]), - red: colorRamp(chroma("#88C0D0")), - orange: colorRamp(chroma("#81A1C1")), - yellow: colorRamp(chroma("#5E81AC")), - green: colorRamp(chroma("#BF616A")), - cyan: colorRamp(chroma("#D08770")), - blue: colorRamp(chroma("#EBCB8B")), - violet: colorRamp(chroma("#A3BE8C")), - magenta: colorRamp(chroma("#B48EAD")), -}); + neutral: chroma.scale([ + "#2E3440", + "#3B4252", + "#434C5E", + "#4C566A", + "#D8DEE9", + "#E5E9F0", + "#ECEFF4", + "#8FBCBB", + ]), + red: colorRamp(chroma("#88C0D0")), + orange: colorRamp(chroma("#81A1C1")), + yellow: colorRamp(chroma("#5E81AC")), + green: colorRamp(chroma("#BF616A")), + cyan: colorRamp(chroma("#D08770")), + blue: colorRamp(chroma("#EBCB8B")), + violet: colorRamp(chroma("#A3BE8C")), + magenta: colorRamp(chroma("#B48EAD")), +}) diff --git a/styles/src/themes/staff/seti-ui.ts b/styles/src/themes/staff/seti-ui.ts index d1c809f6d92f06edd1d67ee662684514674f90b8..7a675829fb3707c26c3baa7fd742cb9eff4b50c8 100644 --- a/styles/src/themes/staff/seti-ui.ts +++ b/styles/src/themes/staff/seti-ui.ts @@ -1,32 +1,32 @@ -import chroma from "chroma-js"; -import { colorRamp, createColorScheme } from "../common/ramps"; +import chroma from "chroma-js" +import { colorRamp, createColorScheme } from "../common/ramps" -const name = "Seti UI"; -const author = "jesseweed"; -const url = "https://github.com/jesseweed/seti-ui"; +const name = "Seti UI" +const author = "jesseweed" +const url = "https://github.com/jesseweed/seti-ui" const license = { - type: "MIT", - url: "https://github.com/jesseweed/seti-ui/blob/master/LICENSE.md", -}; + type: "MIT", + url: "https://github.com/jesseweed/seti-ui/blob/master/LICENSE.md", +} // `name-[light|dark]`, isLight, color ramps export const dark = createColorScheme(`${name}`, false, { - neutral: chroma.scale([ - "#151718", - "#262B30", - "#1E2326", - "#41535B", - "#43a5d5", - "#d6d6d6", - "#eeeeee", - "#ffffff", - ]), - red: colorRamp(chroma("#Cd3f45")), - orange: colorRamp(chroma("#db7b55")), - yellow: colorRamp(chroma("#e6cd69")), - green: colorRamp(chroma("#9fca56")), - cyan: colorRamp(chroma("#55dbbe")), - blue: colorRamp(chroma("#55b5db")), - violet: colorRamp(chroma("#a074c4")), - magenta: colorRamp(chroma("#8a553f")), -}); + neutral: chroma.scale([ + "#151718", + "#262B30", + "#1E2326", + "#41535B", + "#43a5d5", + "#d6d6d6", + "#eeeeee", + "#ffffff", + ]), + red: colorRamp(chroma("#Cd3f45")), + orange: colorRamp(chroma("#db7b55")), + yellow: colorRamp(chroma("#e6cd69")), + green: colorRamp(chroma("#9fca56")), + cyan: colorRamp(chroma("#55dbbe")), + blue: colorRamp(chroma("#55b5db")), + violet: colorRamp(chroma("#a074c4")), + magenta: colorRamp(chroma("#8a553f")), +}) diff --git a/styles/src/themes/staff/tokyo-night-storm.ts b/styles/src/themes/staff/tokyo-night-storm.ts index c7b52c159f51b174ba418619e9e2b4fa62a1e82e..ae375485e8265933dbb26d403bd7fa5dcfb8f3a8 100644 --- a/styles/src/themes/staff/tokyo-night-storm.ts +++ b/styles/src/themes/staff/tokyo-night-storm.ts @@ -1,32 +1,32 @@ -import chroma from "chroma-js"; -import { colorRamp, createColorScheme } from "../common/ramps"; +import chroma from "chroma-js" +import { colorRamp, createColorScheme } from "../common/ramps" -const name = "Tokyo Night Storm"; -const author = "folke"; -const url = "https://github.com/folke/tokyonight.nvim"; +const name = "Tokyo Night Storm" +const author = "folke" +const url = "https://github.com/folke/tokyonight.nvim" const license = { - type: "MIT", - url: "https://github.com/ghifarit53/tokyonight-vim/blob/master/LICENSE", -}; + type: "MIT", + url: "https://github.com/ghifarit53/tokyonight-vim/blob/master/LICENSE", +} // `name-[light|dark]`, isLight, color ramps export const dark = createColorScheme(`${name}`, false, { - neutral: chroma.scale([ - "#24283B", - "#16161E", - "#343A52", - "#444B6A", - "#787C99", - "#A9B1D6", - "#CBCCD1", - "#D5D6DB", - ]), - red: colorRamp(chroma("#C0CAF5")), - orange: colorRamp(chroma("#A9B1D6")), - yellow: colorRamp(chroma("#0DB9D7")), - green: colorRamp(chroma("#9ECE6A")), - cyan: colorRamp(chroma("#B4F9F8")), - blue: colorRamp(chroma("#2AC3DE")), - violet: colorRamp(chroma("#BB9AF7")), - magenta: colorRamp(chroma("#F7768E")), -}); + neutral: chroma.scale([ + "#24283B", + "#16161E", + "#343A52", + "#444B6A", + "#787C99", + "#A9B1D6", + "#CBCCD1", + "#D5D6DB", + ]), + red: colorRamp(chroma("#C0CAF5")), + orange: colorRamp(chroma("#A9B1D6")), + yellow: colorRamp(chroma("#0DB9D7")), + green: colorRamp(chroma("#9ECE6A")), + cyan: colorRamp(chroma("#B4F9F8")), + blue: colorRamp(chroma("#2AC3DE")), + violet: colorRamp(chroma("#BB9AF7")), + magenta: colorRamp(chroma("#F7768E")), +}) diff --git a/styles/src/themes/staff/tokyo-night.ts b/styles/src/themes/staff/tokyo-night.ts index 783c45d50e4ccf67315e3b9797ec566f4180f234..267b1fc031b311e3a57c479c9978041f9b7cbbeb 100644 --- a/styles/src/themes/staff/tokyo-night.ts +++ b/styles/src/themes/staff/tokyo-night.ts @@ -1,53 +1,53 @@ -import chroma from "chroma-js"; -import { colorRamp, createColorScheme } from "../common/ramps"; +import chroma from "chroma-js" +import { colorRamp, createColorScheme } from "../common/ramps" -const name = "Tokyo"; -const author = "folke"; -const url = "https://github.com/folke/tokyonight.nvim"; +const name = "Tokyo" +const author = "folke" +const url = "https://github.com/folke/tokyonight.nvim" const license = { - type: "Apache License 2.0", - url: "https://github.com/folke/tokyonight.nvim/blob/main/LICENSE", -}; + type: "Apache License 2.0", + url: "https://github.com/folke/tokyonight.nvim/blob/main/LICENSE", +} // `name-[light|dark]`, isLight, color ramps export const dark = createColorScheme(`${name} Night`, false, { - neutral: chroma.scale([ - "#1A1B26", - "#16161E", - "#2F3549", - "#444B6A", - "#787C99", - "#A9B1D6", - "#CBCCD1", - "#D5D6DB", - ]), - red: colorRamp(chroma("#C0CAF5")), - orange: colorRamp(chroma("#A9B1D6")), - yellow: colorRamp(chroma("#0DB9D7")), - green: colorRamp(chroma("#9ECE6A")), - cyan: colorRamp(chroma("#B4F9F8")), - blue: colorRamp(chroma("#2AC3DE")), - violet: colorRamp(chroma("#BB9AF7")), - magenta: colorRamp(chroma("#F7768E")), -}); + neutral: chroma.scale([ + "#1A1B26", + "#16161E", + "#2F3549", + "#444B6A", + "#787C99", + "#A9B1D6", + "#CBCCD1", + "#D5D6DB", + ]), + red: colorRamp(chroma("#C0CAF5")), + orange: colorRamp(chroma("#A9B1D6")), + yellow: colorRamp(chroma("#0DB9D7")), + green: colorRamp(chroma("#9ECE6A")), + cyan: colorRamp(chroma("#B4F9F8")), + blue: colorRamp(chroma("#2AC3DE")), + violet: colorRamp(chroma("#BB9AF7")), + magenta: colorRamp(chroma("#F7768E")), +}) export const light = createColorScheme(`${name} Day`, true, { - neutral: chroma.scale([ - "#1A1B26", - "#1A1B26", - "#343B59", - "#4C505E", - "#9699A3", - "#DFE0E5", - "#CBCCD1", - "#D5D6DB", - ]), - red: colorRamp(chroma("#343B58")), - orange: colorRamp(chroma("#965027")), - yellow: colorRamp(chroma("#166775")), - green: colorRamp(chroma("#485E30")), - cyan: colorRamp(chroma("#3E6968")), - blue: colorRamp(chroma("#34548A")), - violet: colorRamp(chroma("#5A4A78")), - magenta: colorRamp(chroma("#8C4351")), -}); + neutral: chroma.scale([ + "#1A1B26", + "#1A1B26", + "#343B59", + "#4C505E", + "#9699A3", + "#DFE0E5", + "#CBCCD1", + "#D5D6DB", + ]), + red: colorRamp(chroma("#343B58")), + orange: colorRamp(chroma("#965027")), + yellow: colorRamp(chroma("#166775")), + green: colorRamp(chroma("#485E30")), + cyan: colorRamp(chroma("#3E6968")), + blue: colorRamp(chroma("#34548A")), + violet: colorRamp(chroma("#5A4A78")), + magenta: colorRamp(chroma("#8C4351")), +}) diff --git a/styles/src/themes/staff/zed-pro.ts b/styles/src/themes/staff/zed-pro.ts index 38f3268930049273298ba57870734677747c4603..9b748be2991fc4dd49785537f07bdb0056436840 100644 --- a/styles/src/themes/staff/zed-pro.ts +++ b/styles/src/themes/staff/zed-pro.ts @@ -1,36 +1,36 @@ -import chroma from "chroma-js"; -import { colorRamp, createColorScheme } from "../common/ramps"; +import chroma from "chroma-js" +import { colorRamp, createColorScheme } from "../common/ramps" -const name = "Zed Pro"; +const name = "Zed Pro" const author = "Nate Butler" const url = "https://github.com/iamnbutler" const license = { - type: "?", - url: "?", -}; + type: "?", + url: "?", +} 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")), -}; + 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); +export const dark = createColorScheme(`${name} Dark`, false, ramps) +export const light = createColorScheme(`${name} Light`, true, ramps) diff --git a/styles/src/themes/staff/zenburn.ts b/styles/src/themes/staff/zenburn.ts index e1ac0415432ae4a61627f0359c3bd593926a416b..648d918a42b2e19b4af3ff31577399e71bba8b89 100644 --- a/styles/src/themes/staff/zenburn.ts +++ b/styles/src/themes/staff/zenburn.ts @@ -1,32 +1,32 @@ -import chroma from "chroma-js"; -import { colorRamp, createColorScheme } from "../common/ramps"; +import chroma from "chroma-js" +import { colorRamp, createColorScheme } from "../common/ramps" -const name = "Zenburn"; -const author = "elnawe"; -const url = "https://github.com/elnawe/base16-zenburn-scheme"; +const name = "Zenburn" +const author = "elnawe" +const url = "https://github.com/elnawe/base16-zenburn-scheme" const license = { - type: "None", - url: "", -}; + type: "None", + url: "", +} // `name-[light|dark]`, isLight, color ramps export const dark = createColorScheme(`${name}`, false, { - neutral: chroma.scale([ - "#383838", - "#404040", - "#606060", - "#6f6f6f", - "#808080", - "#dcdccc", - "#c0c0c0", - "#ffffff", - ]), - red: colorRamp(chroma("#dca3a3")), - orange: colorRamp(chroma("#dfaf8f")), - yellow: colorRamp(chroma("#e0cf9f")), - green: colorRamp(chroma("#5f7f5f")), - cyan: colorRamp(chroma("#93e0e3")), - blue: colorRamp(chroma("#7cb8bb")), - violet: colorRamp(chroma("#dc8cc3")), - magenta: colorRamp(chroma("#000000")), -}); + neutral: chroma.scale([ + "#383838", + "#404040", + "#606060", + "#6f6f6f", + "#808080", + "#dcdccc", + "#c0c0c0", + "#ffffff", + ]), + red: colorRamp(chroma("#dca3a3")), + orange: colorRamp(chroma("#dfaf8f")), + yellow: colorRamp(chroma("#e0cf9f")), + green: colorRamp(chroma("#5f7f5f")), + cyan: colorRamp(chroma("#93e0e3")), + blue: colorRamp(chroma("#7cb8bb")), + violet: colorRamp(chroma("#dc8cc3")), + magenta: colorRamp(chroma("#000000")), +}) diff --git a/styles/src/themes/summercamp.ts b/styles/src/themes/summercamp.ts index 60e0b1834d06bf7c9f1c58fba402cbb9605a9f08..42d4b1a2e4b77500c7c6016499ff2126b9880513 100644 --- a/styles/src/themes/summercamp.ts +++ b/styles/src/themes/summercamp.ts @@ -1,40 +1,42 @@ -import chroma from "chroma-js"; -import { Meta } from "./common/colorScheme"; -import { colorRamp, createColorScheme } from "./common/ramps"; +import chroma from "chroma-js" +import { Meta } from "./common/colorScheme" +import { colorRamp, createColorScheme } from "./common/ramps" -const name = "Summercamp"; +const name = "Summercamp" const ramps = { - 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")), - green: colorRamp(chroma("#5ceb5a")), - cyan: colorRamp(chroma("#5aebbc")), - blue: colorRamp(chroma("#489bf0")), - violet: colorRamp(chroma("#FF8080")), - magenta: colorRamp(chroma("#F69BE7")), -}; + 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")), + green: colorRamp(chroma("#5ceb5a")), + cyan: colorRamp(chroma("#5aebbc")), + blue: colorRamp(chroma("#489bf0")), + violet: colorRamp(chroma("#FF8080")), + magenta: colorRamp(chroma("#F69BE7")), +} -export const dark = createColorScheme(`${name}`, false, ramps); +export const dark = createColorScheme(`${name}`, false, ramps) export const meta: Meta = { - name, - author: "zoefiri", - url: "https://github.com/zoefiri/base16-sc", - license: { - SPDX: "MIT", - https_url: "https://raw.githubusercontent.com/zoefiri/base16-sc/master/LICENSE", - license_checksum: "fadcc834b7eaf2943800956600e8aeea4b495ecf6490f4c4b6c91556a90accaf" - } -} \ No newline at end of file + name, + author: "zoefiri", + url: "https://github.com/zoefiri/base16-sc", + license: { + SPDX: "MIT", + https_url: + "https://raw.githubusercontent.com/zoefiri/base16-sc/master/LICENSE", + license_checksum: + "fadcc834b7eaf2943800956600e8aeea4b495ecf6490f4c4b6c91556a90accaf", + }, +} diff --git a/styles/src/utils/color.ts b/styles/src/utils/color.ts index 7d2d1dd5353a97073861ddbfad80e66873b07ddf..58ee4ccc7c236f4312b3dd5658c0cb5acff7030c 100644 --- a/styles/src/utils/color.ts +++ b/styles/src/utils/color.ts @@ -1,5 +1,5 @@ -import chroma from "chroma-js"; +import chroma from "chroma-js" export function withOpacity(color: string, opacity: number): string { - return chroma(color).alpha(opacity).hex(); + return chroma(color).alpha(opacity).hex() } diff --git a/styles/src/utils/snakeCase.ts b/styles/src/utils/snakeCase.ts index b95c28d89fb54611efa9804a79226c38e32448d9..519106470783da85ebee364752a7a4e86eb879e7 100644 --- a/styles/src/utils/snakeCase.ts +++ b/styles/src/utils/snakeCase.ts @@ -1,35 +1,35 @@ -import { snakeCase } from "case-anything"; +import { snakeCase } from "case-anything" // https://stackoverflow.com/questions/60269936/typescript-convert-generic-object-from-snake-to-camel-case // Typescript magic to convert any string from camelCase to snake_case at compile time type SnakeCase = S extends string - ? S extends `${infer T}${infer U}` - ? `${T extends Capitalize ? "_" : ""}${Lowercase}${SnakeCase}` + ? S extends `${infer T}${infer U}` + ? `${T extends Capitalize ? "_" : ""}${Lowercase}${SnakeCase}` + : S : S - : S; type SnakeCased = { - [Property in keyof Type as SnakeCase]: SnakeCased; -}; + [Property in keyof Type as SnakeCase]: SnakeCased +} export default function snakeCaseTree(object: T): SnakeCased { - const snakeObject: any = {}; - for (const key in object) { - snakeObject[snakeCase(key, { keepSpecialCharacters: true })] = - snakeCaseValue(object[key]); - } - return snakeObject; + const snakeObject: any = {} + for (const key in object) { + snakeObject[snakeCase(key, { keepSpecialCharacters: true })] = + snakeCaseValue(object[key]) + } + return snakeObject } function snakeCaseValue(value: any): any { - if (typeof value === "object") { - if (Array.isArray(value)) { - return value.map(snakeCaseValue); + if (typeof value === "object") { + if (Array.isArray(value)) { + return value.map(snakeCaseValue) + } else { + return snakeCaseTree(value) + } } else { - return snakeCaseTree(value); + return value } - } else { - return value; - } } diff --git a/styles/tsconfig.json b/styles/tsconfig.json index fc09fdb0a456b6ba845d9d3483cf4fac1e9e5c4f..13a5faf32b4178df8589be8527a75680ba8487af 100644 --- a/styles/tsconfig.json +++ b/styles/tsconfig.json @@ -1,12 +1,12 @@ { - "compilerOptions": { - "target": "es2015", - "module": "commonjs", - "esModuleInterop": true, - "noImplicitAny": true, - "removeComments": true, - "preserveConstEnums": true, - "sourceMap": true - }, - "exclude": ["node_modules"] + "compilerOptions": { + "target": "es2015", + "module": "commonjs", + "esModuleInterop": true, + "noImplicitAny": true, + "removeComments": true, + "preserveConstEnums": true, + "sourceMap": true + }, + "exclude": ["node_modules"] }