mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-20 10:57:22 +03:00
Merge pull request #1049 from zed-industries/invite-codes-2
Support inviting new Zed users
This commit is contained in:
commit
d30d2d67e7
12
Cargo.lock
generated
12
Cargo.lock
generated
@ -851,11 +851,13 @@ dependencies = [
|
||||
"envy",
|
||||
"futures",
|
||||
"gpui",
|
||||
"hyper",
|
||||
"language",
|
||||
"lazy_static",
|
||||
"lipsum",
|
||||
"log",
|
||||
"lsp",
|
||||
"nanoid",
|
||||
"opentelemetry",
|
||||
"opentelemetry-otlp",
|
||||
"parking_lot",
|
||||
@ -934,6 +936,7 @@ checksum = "f92cfa0fd5690b3cf8c1ef2cabbd9b7ef22fa53cf5e1f92b05103f6d5d1cf6e7"
|
||||
name = "contacts_panel"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"client",
|
||||
"editor",
|
||||
"futures",
|
||||
@ -2762,6 +2765,15 @@ version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a"
|
||||
|
||||
[[package]]
|
||||
name = "nanoid"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8"
|
||||
dependencies = [
|
||||
"rand 0.8.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "native-tls"
|
||||
version = "0.2.10"
|
||||
|
1734
assets/themes/cave-dark.json
Normal file
1734
assets/themes/cave-dark.json
Normal file
File diff suppressed because it is too large
Load Diff
1734
assets/themes/cave-light.json
Normal file
1734
assets/themes/cave-light.json
Normal file
File diff suppressed because it is too large
Load Diff
1734
assets/themes/solarized-dark.json
Normal file
1734
assets/themes/solarized-dark.json
Normal file
File diff suppressed because it is too large
Load Diff
1734
assets/themes/solarized-light.json
Normal file
1734
assets/themes/solarized-light.json
Normal file
File diff suppressed because it is too large
Load Diff
1734
assets/themes/sulphurpool-dark.json
Normal file
1734
assets/themes/sulphurpool-dark.json
Normal file
File diff suppressed because it is too large
Load Diff
1734
assets/themes/sulphurpool-light.json
Normal file
1734
assets/themes/sulphurpool-light.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -65,6 +65,7 @@ pub struct UserStore {
|
||||
incoming_contact_requests: Vec<Arc<User>>,
|
||||
outgoing_contact_requests: Vec<Arc<User>>,
|
||||
pending_contact_requests: HashMap<u64, usize>,
|
||||
invite_info: Option<InviteInfo>,
|
||||
client: Weak<Client>,
|
||||
http: Arc<dyn HttpClient>,
|
||||
_maintain_contacts: Task<()>,
|
||||
@ -72,9 +73,17 @@ pub struct UserStore {
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ContactEvent {
|
||||
pub user: Arc<User>,
|
||||
pub kind: ContactEventKind,
|
||||
pub struct InviteInfo {
|
||||
pub count: u32,
|
||||
pub url: Arc<str>,
|
||||
}
|
||||
|
||||
pub enum Event {
|
||||
Contact {
|
||||
user: Arc<User>,
|
||||
kind: ContactEventKind,
|
||||
},
|
||||
ShowContacts,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
@ -85,7 +94,7 @@ pub enum ContactEventKind {
|
||||
}
|
||||
|
||||
impl Entity for UserStore {
|
||||
type Event = ContactEvent;
|
||||
type Event = Event;
|
||||
}
|
||||
|
||||
enum UpdateContacts {
|
||||
@ -101,19 +110,23 @@ impl UserStore {
|
||||
) -> Self {
|
||||
let (mut current_user_tx, current_user_rx) = watch::channel();
|
||||
let (update_contacts_tx, mut update_contacts_rx) = mpsc::unbounded();
|
||||
let rpc_subscription =
|
||||
client.add_message_handler(cx.handle(), Self::handle_update_contacts);
|
||||
let rpc_subscriptions = vec![
|
||||
client.add_message_handler(cx.handle(), Self::handle_update_contacts),
|
||||
client.add_message_handler(cx.handle(), Self::handle_update_invite_info),
|
||||
client.add_message_handler(cx.handle(), Self::handle_show_contacts),
|
||||
];
|
||||
Self {
|
||||
users: Default::default(),
|
||||
current_user: current_user_rx,
|
||||
contacts: Default::default(),
|
||||
incoming_contact_requests: Default::default(),
|
||||
outgoing_contact_requests: Default::default(),
|
||||
invite_info: None,
|
||||
client: Arc::downgrade(&client),
|
||||
update_contacts_tx,
|
||||
http,
|
||||
_maintain_contacts: cx.spawn_weak(|this, mut cx| async move {
|
||||
let _subscription = rpc_subscription;
|
||||
let _subscriptions = rpc_subscriptions;
|
||||
while let Some(message) = update_contacts_rx.next().await {
|
||||
if let Some(this) = this.upgrade(&cx) {
|
||||
this.update(&mut cx, |this, cx| this.update_contacts(message, cx))
|
||||
@ -154,15 +167,45 @@ impl UserStore {
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_update_invite_info(
|
||||
this: ModelHandle<Self>,
|
||||
message: TypedEnvelope<proto::UpdateInviteInfo>,
|
||||
_: Arc<Client>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.invite_info = Some(InviteInfo {
|
||||
url: Arc::from(message.payload.url),
|
||||
count: message.payload.count,
|
||||
});
|
||||
cx.notify();
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_show_contacts(
|
||||
this: ModelHandle<Self>,
|
||||
_: TypedEnvelope<proto::ShowContacts>,
|
||||
_: Arc<Client>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
this.update(&mut cx, |_, cx| cx.emit(Event::ShowContacts));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn invite_info(&self) -> Option<&InviteInfo> {
|
||||
self.invite_info.as_ref()
|
||||
}
|
||||
|
||||
async fn handle_update_contacts(
|
||||
this: ModelHandle<Self>,
|
||||
msg: TypedEnvelope<proto::UpdateContacts>,
|
||||
message: TypedEnvelope<proto::UpdateContacts>,
|
||||
_: Arc<Client>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
this.update(&mut cx, |this, _| {
|
||||
this.update_contacts_tx
|
||||
.unbounded_send(UpdateContacts::Update(msg.payload))
|
||||
.unbounded_send(UpdateContacts::Update(message.payload))
|
||||
.unwrap();
|
||||
});
|
||||
Ok(())
|
||||
@ -244,7 +287,7 @@ impl UserStore {
|
||||
// Update existing contacts and insert new ones
|
||||
for (updated_contact, should_notify) in updated_contacts {
|
||||
if should_notify {
|
||||
cx.emit(ContactEvent {
|
||||
cx.emit(Event::Contact {
|
||||
user: updated_contact.user.clone(),
|
||||
kind: ContactEventKind::Accepted,
|
||||
});
|
||||
@ -261,7 +304,7 @@ impl UserStore {
|
||||
// Remove incoming contact requests
|
||||
this.incoming_contact_requests.retain(|user| {
|
||||
if removed_incoming_requests.contains(&user.id) {
|
||||
cx.emit(ContactEvent {
|
||||
cx.emit(Event::Contact {
|
||||
user: user.clone(),
|
||||
kind: ContactEventKind::Cancelled,
|
||||
});
|
||||
@ -273,7 +316,7 @@ impl UserStore {
|
||||
// Update existing incoming requests and insert new ones
|
||||
for (user, should_notify) in incoming_requests {
|
||||
if should_notify {
|
||||
cx.emit(ContactEvent {
|
||||
cx.emit(Event::Contact {
|
||||
user: user.clone(),
|
||||
kind: ContactEventKind::Requested,
|
||||
});
|
||||
|
@ -1,2 +1,9 @@
|
||||
DATABASE_URL = "postgres://postgres@localhost/zed"
|
||||
HTTP_PORT = 8080
|
||||
API_TOKEN = "secret"
|
||||
INVITE_LINK_PREFIX = "http://localhost:3000/invites/"
|
||||
|
||||
# HONEYCOMB_API_KEY=
|
||||
# HONEYCOMB_DATASET=
|
||||
# RUST_LOG=info
|
||||
# LOG_JSON=true
|
||||
|
@ -25,8 +25,10 @@ base64 = "0.13"
|
||||
clap = { version = "3.1", features = ["derive"], optional = true }
|
||||
envy = "0.4.2"
|
||||
futures = "0.3"
|
||||
hyper = "0.14"
|
||||
lazy_static = "1.4"
|
||||
lipsum = { version = "0.8", optional = true }
|
||||
nanoid = "0.4"
|
||||
opentelemetry = { version = "0.17", features = ["rt-tokio"] }
|
||||
opentelemetry-otlp = { version = "0.10", features = ["tls-roots"] }
|
||||
parking_lot = "0.11.1"
|
||||
|
@ -1,2 +1,3 @@
|
||||
ZED_ENVIRONMENT=production
|
||||
RUST_LOG=info,rpc=debug
|
||||
INVITE_LINK_PREFIX=https://zed.dev/invites/
|
||||
|
@ -1,2 +1,3 @@
|
||||
ZED_ENVIRONMENT=staging
|
||||
RUST_LOG=info,rpc=debug
|
||||
INVITE_LINK_PREFIX=https://staging.zed.dev/invites/
|
||||
|
@ -81,6 +81,8 @@ spec:
|
||||
secretKeyRef:
|
||||
name: api
|
||||
key: token
|
||||
- name: INVITE_LINK_PREFIX
|
||||
value: ${INVITE_LINK_PREFIX}
|
||||
- name: RUST_LOG
|
||||
value: ${RUST_LOG}
|
||||
- name: LOG_JSON
|
||||
|
@ -0,0 +1,9 @@
|
||||
ALTER TABLE users
|
||||
ADD email_address VARCHAR(255) DEFAULT NULL,
|
||||
ADD invite_code VARCHAR(64),
|
||||
ADD invite_count INTEGER NOT NULL DEFAULT 0,
|
||||
ADD inviter_id INTEGER REFERENCES users (id),
|
||||
ADD connected_once BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD created_at TIMESTAMP NOT NULL DEFAULT NOW();
|
||||
|
||||
CREATE UNIQUE INDEX "index_invite_code_users" ON "users" ("invite_code");
|
@ -0,0 +1,6 @@
|
||||
ALTER TABLE contacts DROP CONSTRAINT contacts_user_id_a_fkey;
|
||||
ALTER TABLE contacts DROP CONSTRAINT contacts_user_id_b_fkey;
|
||||
ALTER TABLE contacts ADD CONSTRAINT contacts_user_id_a_fkey FOREIGN KEY (user_id_a) REFERENCES users(id) ON DELETE CASCADE;
|
||||
ALTER TABLE contacts ADD CONSTRAINT contacts_user_id_b_fkey FOREIGN KEY (user_id_b) REFERENCES users(id) ON DELETE CASCADE;
|
||||
ALTER TABLE users DROP CONSTRAINT users_inviter_id_fkey;
|
||||
ALTER TABLE users ADD CONSTRAINT users_inviter_id_fkey FOREIGN KEY (inviter_id) REFERENCES users(id) ON DELETE SET NULL;
|
@ -1,6 +1,7 @@
|
||||
use crate::{
|
||||
auth,
|
||||
db::{User, UserId},
|
||||
rpc::ResultExt,
|
||||
AppState, Error, Result,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
@ -18,7 +19,7 @@ use std::sync::Arc;
|
||||
use tower::ServiceBuilder;
|
||||
use tracing::instrument;
|
||||
|
||||
pub fn routes(state: Arc<AppState>) -> Router<Body> {
|
||||
pub fn routes(rpc_server: &Arc<crate::rpc::Server>, state: Arc<AppState>) -> Router<Body> {
|
||||
Router::new()
|
||||
.route("/users", get(get_users).post(create_user))
|
||||
.route(
|
||||
@ -26,13 +27,14 @@ pub fn routes(state: Arc<AppState>) -> Router<Body> {
|
||||
put(update_user).delete(destroy_user).get(get_user),
|
||||
)
|
||||
.route("/users/:id/access_tokens", post(create_access_token))
|
||||
.route("/invite_codes/:code", get(get_user_for_invite_code))
|
||||
.route("/panic", post(trace_panic))
|
||||
.layer(
|
||||
ServiceBuilder::new()
|
||||
.layer(Extension(state))
|
||||
.layer(Extension(rpc_server.clone()))
|
||||
.layer(middleware::from_fn(validate_api_token)),
|
||||
)
|
||||
// TODO: Compression on API routes?
|
||||
}
|
||||
|
||||
pub async fn validate_api_token<B>(req: Request<B>, next: Next<B>) -> impl IntoResponse {
|
||||
@ -71,20 +73,44 @@ async fn get_users(Extension(app): Extension<Arc<AppState>>) -> Result<Json<Vec<
|
||||
Ok(Json(users))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct CreateUserParams {
|
||||
github_login: String,
|
||||
invite_code: Option<String>,
|
||||
email_address: Option<String>,
|
||||
admin: bool,
|
||||
}
|
||||
|
||||
async fn create_user(
|
||||
Json(params): Json<CreateUserParams>,
|
||||
Extension(app): Extension<Arc<AppState>>,
|
||||
Extension(rpc_server): Extension<Arc<crate::rpc::Server>>,
|
||||
) -> Result<Json<User>> {
|
||||
let user_id = app
|
||||
.db
|
||||
.create_user(¶ms.github_login, params.admin)
|
||||
.await?;
|
||||
println!("{:?}", params);
|
||||
|
||||
let user_id = if let Some(invite_code) = params.invite_code {
|
||||
let invitee_id = app
|
||||
.db
|
||||
.redeem_invite_code(
|
||||
&invite_code,
|
||||
¶ms.github_login,
|
||||
params.email_address.as_deref(),
|
||||
)
|
||||
.await?;
|
||||
rpc_server
|
||||
.invite_code_redeemed(&invite_code, invitee_id)
|
||||
.await
|
||||
.trace_err();
|
||||
invitee_id
|
||||
} else {
|
||||
app.db
|
||||
.create_user(
|
||||
¶ms.github_login,
|
||||
params.email_address.as_deref(),
|
||||
params.admin,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
|
||||
let user = app
|
||||
.db
|
||||
@ -97,7 +123,8 @@ async fn create_user(
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct UpdateUserParams {
|
||||
admin: bool,
|
||||
admin: Option<bool>,
|
||||
invite_count: Option<u32>,
|
||||
}
|
||||
|
||||
async fn update_user(
|
||||
@ -105,9 +132,16 @@ async fn update_user(
|
||||
Json(params): Json<UpdateUserParams>,
|
||||
Extension(app): Extension<Arc<AppState>>,
|
||||
) -> Result<()> {
|
||||
app.db
|
||||
.set_user_is_admin(UserId(user_id), params.admin)
|
||||
.await?;
|
||||
if let Some(admin) = params.admin {
|
||||
app.db.set_user_is_admin(UserId(user_id), admin).await?;
|
||||
}
|
||||
|
||||
if let Some(invite_count) = params.invite_count {
|
||||
app.db
|
||||
.set_invite_count(UserId(user_id), invite_count)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -127,7 +161,7 @@ async fn get_user(
|
||||
.db
|
||||
.get_user_by_github_login(&login)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow!("user not found"))?;
|
||||
.ok_or_else(|| Error::Http(StatusCode::NOT_FOUND, "User not found".to_string()))?;
|
||||
Ok(Json(user))
|
||||
}
|
||||
|
||||
@ -196,3 +230,10 @@ async fn create_access_token(
|
||||
encrypted_access_token,
|
||||
}))
|
||||
}
|
||||
|
||||
async fn get_user_for_invite_code(
|
||||
Path(code): Path<String>,
|
||||
Extension(app): Extension<Arc<AppState>>,
|
||||
) -> Result<Json<User>> {
|
||||
Ok(Json(app.db.get_user_for_invite_code(&code).await?))
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::db::{self, UserId};
|
||||
use crate::{AppState, Error};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use crate::{AppState, Error, Result};
|
||||
use anyhow::{anyhow, Context};
|
||||
use axum::{
|
||||
http::{self, Request, StatusCode},
|
||||
middleware::Next,
|
||||
@ -91,7 +91,8 @@ fn hash_access_token(token: &str) -> Result<String> {
|
||||
None,
|
||||
params,
|
||||
&SaltString::generate(thread_rng()),
|
||||
)?
|
||||
)
|
||||
.map_err(anyhow::Error::new)?
|
||||
.to_string())
|
||||
}
|
||||
|
||||
@ -105,6 +106,6 @@ pub fn encrypt_access_token(access_token: &str, public_key: String) -> Result<St
|
||||
}
|
||||
|
||||
pub fn verify_access_token(token: &str, hash: &str) -> Result<bool> {
|
||||
let hash = PasswordHash::new(hash)?;
|
||||
let hash = PasswordHash::new(hash).map_err(anyhow::Error::new)?;
|
||||
Ok(Scrypt.verify_password(token.as_bytes(), &hash).is_ok())
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use crate::{Error, Result};
|
||||
use anyhow::{anyhow, Context};
|
||||
use async_trait::async_trait;
|
||||
use axum::http::StatusCode;
|
||||
use futures::StreamExt;
|
||||
use nanoid::nanoid;
|
||||
use serde::Serialize;
|
||||
pub use sqlx::postgres::PgPoolOptions as DbOptions;
|
||||
use sqlx::{types::Uuid, FromRow};
|
||||
@ -8,15 +11,31 @@ use time::OffsetDateTime;
|
||||
|
||||
#[async_trait]
|
||||
pub trait Db: Send + Sync {
|
||||
async fn create_user(&self, github_login: &str, admin: bool) -> Result<UserId>;
|
||||
async fn create_user(
|
||||
&self,
|
||||
github_login: &str,
|
||||
email_address: Option<&str>,
|
||||
admin: bool,
|
||||
) -> Result<UserId>;
|
||||
async fn get_all_users(&self) -> Result<Vec<User>>;
|
||||
async fn fuzzy_search_users(&self, query: &str, limit: u32) -> Result<Vec<User>>;
|
||||
async fn get_user_by_id(&self, id: UserId) -> Result<Option<User>>;
|
||||
async fn get_users_by_ids(&self, ids: Vec<UserId>) -> Result<Vec<User>>;
|
||||
async fn get_user_by_github_login(&self, github_login: &str) -> Result<Option<User>>;
|
||||
async fn set_user_is_admin(&self, id: UserId, is_admin: bool) -> Result<()>;
|
||||
async fn set_user_connected_once(&self, id: UserId, connected_once: bool) -> Result<()>;
|
||||
async fn destroy_user(&self, id: UserId) -> Result<()>;
|
||||
|
||||
async fn set_invite_count(&self, id: UserId, count: u32) -> Result<()>;
|
||||
async fn get_invite_code_for_user(&self, id: UserId) -> Result<Option<(String, u32)>>;
|
||||
async fn get_user_for_invite_code(&self, code: &str) -> Result<User>;
|
||||
async fn redeem_invite_code(
|
||||
&self,
|
||||
code: &str,
|
||||
login: &str,
|
||||
email_address: Option<&str>,
|
||||
) -> Result<UserId>;
|
||||
|
||||
async fn get_contacts(&self, id: UserId) -> Result<Vec<Contact>>;
|
||||
async fn has_contact(&self, user_id_a: UserId, user_id_b: UserId) -> Result<bool>;
|
||||
async fn send_contact_request(&self, requester_id: UserId, responder_id: UserId) -> Result<()>;
|
||||
@ -101,15 +120,21 @@ impl PostgresDb {
|
||||
impl Db for PostgresDb {
|
||||
// users
|
||||
|
||||
async fn create_user(&self, github_login: &str, admin: bool) -> Result<UserId> {
|
||||
async fn create_user(
|
||||
&self,
|
||||
github_login: &str,
|
||||
email_address: Option<&str>,
|
||||
admin: bool,
|
||||
) -> Result<UserId> {
|
||||
let query = "
|
||||
INSERT INTO users (github_login, admin)
|
||||
VALUES ($1, $2)
|
||||
INSERT INTO users (github_login, email_address, admin)
|
||||
VALUES ($1, $2, $3)
|
||||
ON CONFLICT (github_login) DO UPDATE SET github_login = excluded.github_login
|
||||
RETURNING id
|
||||
";
|
||||
Ok(sqlx::query_scalar(query)
|
||||
.bind(github_login)
|
||||
.bind(email_address)
|
||||
.bind(admin)
|
||||
.fetch_one(&self.pool)
|
||||
.await
|
||||
@ -174,6 +199,16 @@ impl Db for PostgresDb {
|
||||
.map(drop)?)
|
||||
}
|
||||
|
||||
async fn set_user_connected_once(&self, id: UserId, connected_once: bool) -> Result<()> {
|
||||
let query = "UPDATE users SET connected_once = $1 WHERE id = $2";
|
||||
Ok(sqlx::query(query)
|
||||
.bind(connected_once)
|
||||
.bind(id.0)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map(drop)?)
|
||||
}
|
||||
|
||||
async fn destroy_user(&self, id: UserId) -> Result<()> {
|
||||
let query = "DELETE FROM access_tokens WHERE user_id = $1;";
|
||||
sqlx::query(query)
|
||||
@ -189,6 +224,153 @@ impl Db for PostgresDb {
|
||||
.map(drop)?)
|
||||
}
|
||||
|
||||
// invite codes
|
||||
|
||||
async fn set_invite_count(&self, id: UserId, count: u32) -> Result<()> {
|
||||
let mut tx = self.pool.begin().await?;
|
||||
if count > 0 {
|
||||
sqlx::query(
|
||||
"
|
||||
UPDATE users
|
||||
SET invite_code = $1
|
||||
WHERE id = $2 AND invite_code IS NULL
|
||||
",
|
||||
)
|
||||
.bind(nanoid!(16))
|
||||
.bind(id)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
}
|
||||
|
||||
sqlx::query(
|
||||
"
|
||||
UPDATE users
|
||||
SET invite_count = $1
|
||||
WHERE id = $2
|
||||
",
|
||||
)
|
||||
.bind(count)
|
||||
.bind(id)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_invite_code_for_user(&self, id: UserId) -> Result<Option<(String, u32)>> {
|
||||
let result: Option<(String, i32)> = sqlx::query_as(
|
||||
"
|
||||
SELECT invite_code, invite_count
|
||||
FROM users
|
||||
WHERE id = $1 AND invite_code IS NOT NULL
|
||||
",
|
||||
)
|
||||
.bind(id)
|
||||
.fetch_optional(&self.pool)
|
||||
.await?;
|
||||
if let Some((code, count)) = result {
|
||||
Ok(Some((code, count.try_into().map_err(anyhow::Error::new)?)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_user_for_invite_code(&self, code: &str) -> Result<User> {
|
||||
sqlx::query_as(
|
||||
"
|
||||
SELECT *
|
||||
FROM users
|
||||
WHERE invite_code = $1
|
||||
",
|
||||
)
|
||||
.bind(code)
|
||||
.fetch_optional(&self.pool)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
Error::Http(
|
||||
StatusCode::NOT_FOUND,
|
||||
"that invite code does not exist".to_string(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
async fn redeem_invite_code(
|
||||
&self,
|
||||
code: &str,
|
||||
login: &str,
|
||||
email_address: Option<&str>,
|
||||
) -> Result<UserId> {
|
||||
let mut tx = self.pool.begin().await?;
|
||||
|
||||
let inviter_id: Option<UserId> = sqlx::query_scalar(
|
||||
"
|
||||
UPDATE users
|
||||
SET invite_count = invite_count - 1
|
||||
WHERE
|
||||
invite_code = $1 AND
|
||||
invite_count > 0
|
||||
RETURNING id
|
||||
",
|
||||
)
|
||||
.bind(code)
|
||||
.fetch_optional(&mut tx)
|
||||
.await?;
|
||||
|
||||
let inviter_id = match inviter_id {
|
||||
Some(inviter_id) => inviter_id,
|
||||
None => {
|
||||
if sqlx::query_scalar::<_, i32>("SELECT 1 FROM users WHERE invite_code = $1")
|
||||
.bind(code)
|
||||
.fetch_optional(&mut tx)
|
||||
.await?
|
||||
.is_some()
|
||||
{
|
||||
Err(Error::Http(
|
||||
StatusCode::UNAUTHORIZED,
|
||||
"no invites remaining".to_string(),
|
||||
))?
|
||||
} else {
|
||||
Err(Error::Http(
|
||||
StatusCode::NOT_FOUND,
|
||||
"invite code not found".to_string(),
|
||||
))?
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let invitee_id = sqlx::query_scalar(
|
||||
"
|
||||
INSERT INTO users
|
||||
(github_login, email_address, admin, inviter_id)
|
||||
VALUES
|
||||
($1, $2, 'f', $3)
|
||||
RETURNING id
|
||||
",
|
||||
)
|
||||
.bind(login)
|
||||
.bind(email_address)
|
||||
.bind(inviter_id)
|
||||
.fetch_one(&mut tx)
|
||||
.await
|
||||
.map(UserId)?;
|
||||
|
||||
sqlx::query(
|
||||
"
|
||||
INSERT INTO contacts
|
||||
(user_id_a, user_id_b, a_to_b, should_notify, accepted)
|
||||
VALUES
|
||||
($1, $2, 't', 't', 't')
|
||||
",
|
||||
)
|
||||
.bind(inviter_id)
|
||||
.bind(invitee_id)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
|
||||
tx.commit().await?;
|
||||
Ok(invitee_id)
|
||||
}
|
||||
|
||||
// contacts
|
||||
|
||||
async fn get_contacts(&self, user_id: UserId) -> Result<Vec<Contact>> {
|
||||
@ -293,7 +475,7 @@ impl Db for PostgresDb {
|
||||
if result.rows_affected() == 1 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow!("contact already requested"))
|
||||
Err(anyhow!("contact already requested"))?
|
||||
}
|
||||
}
|
||||
|
||||
@ -316,7 +498,7 @@ impl Db for PostgresDb {
|
||||
if result.rows_affected() == 1 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow!("no such contact"))
|
||||
Err(anyhow!("no such contact"))?
|
||||
}
|
||||
}
|
||||
|
||||
@ -394,7 +576,7 @@ impl Db for PostgresDb {
|
||||
if result.rows_affected() == 1 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow!("no such contact request"))
|
||||
Err(anyhow!("no such contact request"))?
|
||||
}
|
||||
}
|
||||
|
||||
@ -694,11 +876,15 @@ macro_rules! id_type {
|
||||
}
|
||||
|
||||
id_type!(UserId);
|
||||
#[derive(Clone, Debug, FromRow, Serialize, PartialEq)]
|
||||
#[derive(Clone, Debug, Default, FromRow, Serialize, PartialEq)]
|
||||
pub struct User {
|
||||
pub id: UserId,
|
||||
pub github_login: String,
|
||||
pub email_address: Option<String>,
|
||||
pub admin: bool,
|
||||
pub invite_code: Option<String>,
|
||||
pub invite_count: i32,
|
||||
pub connected_once: bool,
|
||||
}
|
||||
|
||||
id_type!(OrgId);
|
||||
@ -796,10 +982,10 @@ pub mod tests {
|
||||
] {
|
||||
let db = test_db.db();
|
||||
|
||||
let user = db.create_user("user", false).await.unwrap();
|
||||
let friend1 = db.create_user("friend-1", false).await.unwrap();
|
||||
let friend2 = db.create_user("friend-2", false).await.unwrap();
|
||||
let friend3 = db.create_user("friend-3", false).await.unwrap();
|
||||
let user = db.create_user("user", None, false).await.unwrap();
|
||||
let friend1 = db.create_user("friend-1", None, false).await.unwrap();
|
||||
let friend2 = db.create_user("friend-2", None, false).await.unwrap();
|
||||
let friend3 = db.create_user("friend-3", None, false).await.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
db.get_users_by_ids(vec![user, friend1, friend2, friend3])
|
||||
@ -810,21 +996,25 @@ pub mod tests {
|
||||
id: user,
|
||||
github_login: "user".to_string(),
|
||||
admin: false,
|
||||
..Default::default()
|
||||
},
|
||||
User {
|
||||
id: friend1,
|
||||
github_login: "friend-1".to_string(),
|
||||
admin: false,
|
||||
..Default::default()
|
||||
},
|
||||
User {
|
||||
id: friend2,
|
||||
github_login: "friend-2".to_string(),
|
||||
admin: false,
|
||||
..Default::default()
|
||||
},
|
||||
User {
|
||||
id: friend3,
|
||||
github_login: "friend-3".to_string(),
|
||||
admin: false,
|
||||
..Default::default()
|
||||
}
|
||||
]
|
||||
);
|
||||
@ -838,7 +1028,7 @@ pub mod tests {
|
||||
TestDb::fake(Arc::new(gpui::executor::Background::new())),
|
||||
] {
|
||||
let db = test_db.db();
|
||||
let user = db.create_user("user", false).await.unwrap();
|
||||
let user = db.create_user("user", None, false).await.unwrap();
|
||||
let org = db.create_org("org", "org").await.unwrap();
|
||||
let channel = db.create_org_channel(org, "channel").await.unwrap();
|
||||
for i in 0..10 {
|
||||
@ -877,7 +1067,7 @@ pub mod tests {
|
||||
TestDb::fake(Arc::new(gpui::executor::Background::new())),
|
||||
] {
|
||||
let db = test_db.db();
|
||||
let user = db.create_user("user", false).await.unwrap();
|
||||
let user = db.create_user("user", None, false).await.unwrap();
|
||||
let org = db.create_org("org", "org").await.unwrap();
|
||||
let channel = db.create_org_channel(org, "channel").await.unwrap();
|
||||
|
||||
@ -908,7 +1098,7 @@ pub mod tests {
|
||||
async fn test_create_access_tokens() {
|
||||
let test_db = TestDb::postgres().await;
|
||||
let db = test_db.db();
|
||||
let user = db.create_user("the-user", false).await.unwrap();
|
||||
let user = db.create_user("the-user", None, false).await.unwrap();
|
||||
|
||||
db.create_access_token_hash(user, "h1", 3).await.unwrap();
|
||||
db.create_access_token_hash(user, "h2", 3).await.unwrap();
|
||||
@ -956,7 +1146,7 @@ pub mod tests {
|
||||
"delaware",
|
||||
"rhode-island",
|
||||
] {
|
||||
db.create_user(github_login, false).await.unwrap();
|
||||
db.create_user(github_login, None, false).await.unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
@ -986,9 +1176,9 @@ pub mod tests {
|
||||
] {
|
||||
let db = test_db.db();
|
||||
|
||||
let user_1 = db.create_user("user1", false).await.unwrap();
|
||||
let user_2 = db.create_user("user2", false).await.unwrap();
|
||||
let user_3 = db.create_user("user3", false).await.unwrap();
|
||||
let user_1 = db.create_user("user1", None, false).await.unwrap();
|
||||
let user_2 = db.create_user("user2", None, false).await.unwrap();
|
||||
let user_3 = db.create_user("user3", None, false).await.unwrap();
|
||||
|
||||
// User starts with no contacts
|
||||
assert_eq!(
|
||||
@ -1198,6 +1388,159 @@ pub mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_invite_codes() {
|
||||
let postgres = TestDb::postgres().await;
|
||||
let db = postgres.db();
|
||||
let user1 = db.create_user("user-1", None, false).await.unwrap();
|
||||
|
||||
// Initially, user 1 has no invite code
|
||||
assert_eq!(db.get_invite_code_for_user(user1).await.unwrap(), None);
|
||||
|
||||
// Setting invite count to 0 when no code is assigned does not assign a new code
|
||||
db.set_invite_count(user1, 0).await.unwrap();
|
||||
assert!(db.get_invite_code_for_user(user1).await.unwrap().is_none());
|
||||
|
||||
// User 1 creates an invite code that can be used twice.
|
||||
db.set_invite_count(user1, 2).await.unwrap();
|
||||
let (invite_code, invite_count) =
|
||||
db.get_invite_code_for_user(user1).await.unwrap().unwrap();
|
||||
assert_eq!(invite_count, 2);
|
||||
|
||||
// User 2 redeems the invite code and becomes a contact of user 1.
|
||||
let user2 = db
|
||||
.redeem_invite_code(&invite_code, "user-2", None)
|
||||
.await
|
||||
.unwrap();
|
||||
let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
|
||||
assert_eq!(invite_count, 1);
|
||||
assert_eq!(
|
||||
db.get_contacts(user1).await.unwrap(),
|
||||
[
|
||||
Contact::Accepted {
|
||||
user_id: user1,
|
||||
should_notify: false
|
||||
},
|
||||
Contact::Accepted {
|
||||
user_id: user2,
|
||||
should_notify: true
|
||||
}
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
db.get_contacts(user2).await.unwrap(),
|
||||
[
|
||||
Contact::Accepted {
|
||||
user_id: user1,
|
||||
should_notify: false
|
||||
},
|
||||
Contact::Accepted {
|
||||
user_id: user2,
|
||||
should_notify: false
|
||||
}
|
||||
]
|
||||
);
|
||||
|
||||
// User 3 redeems the invite code and becomes a contact of user 1.
|
||||
let user3 = db
|
||||
.redeem_invite_code(&invite_code, "user-3", None)
|
||||
.await
|
||||
.unwrap();
|
||||
let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
|
||||
assert_eq!(invite_count, 0);
|
||||
assert_eq!(
|
||||
db.get_contacts(user1).await.unwrap(),
|
||||
[
|
||||
Contact::Accepted {
|
||||
user_id: user1,
|
||||
should_notify: false
|
||||
},
|
||||
Contact::Accepted {
|
||||
user_id: user2,
|
||||
should_notify: true
|
||||
},
|
||||
Contact::Accepted {
|
||||
user_id: user3,
|
||||
should_notify: true
|
||||
}
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
db.get_contacts(user3).await.unwrap(),
|
||||
[
|
||||
Contact::Accepted {
|
||||
user_id: user1,
|
||||
should_notify: false
|
||||
},
|
||||
Contact::Accepted {
|
||||
user_id: user3,
|
||||
should_notify: false
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
// Trying to reedem the code for the third time results in an error.
|
||||
db.redeem_invite_code(&invite_code, "user-4", None)
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
// Invite count can be updated after the code has been created.
|
||||
db.set_invite_count(user1, 2).await.unwrap();
|
||||
let (latest_code, invite_count) =
|
||||
db.get_invite_code_for_user(user1).await.unwrap().unwrap();
|
||||
assert_eq!(latest_code, invite_code); // Invite code doesn't change when we increment above 0
|
||||
assert_eq!(invite_count, 2);
|
||||
|
||||
// User 4 can now redeem the invite code and becomes a contact of user 1.
|
||||
let user4 = db
|
||||
.redeem_invite_code(&invite_code, "user-4", None)
|
||||
.await
|
||||
.unwrap();
|
||||
let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
|
||||
assert_eq!(invite_count, 1);
|
||||
assert_eq!(
|
||||
db.get_contacts(user1).await.unwrap(),
|
||||
[
|
||||
Contact::Accepted {
|
||||
user_id: user1,
|
||||
should_notify: false
|
||||
},
|
||||
Contact::Accepted {
|
||||
user_id: user2,
|
||||
should_notify: true
|
||||
},
|
||||
Contact::Accepted {
|
||||
user_id: user3,
|
||||
should_notify: true
|
||||
},
|
||||
Contact::Accepted {
|
||||
user_id: user4,
|
||||
should_notify: true
|
||||
}
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
db.get_contacts(user4).await.unwrap(),
|
||||
[
|
||||
Contact::Accepted {
|
||||
user_id: user1,
|
||||
should_notify: false
|
||||
},
|
||||
Contact::Accepted {
|
||||
user_id: user4,
|
||||
should_notify: false
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
// An existing user cannot redeem invite codes.
|
||||
db.redeem_invite_code(&invite_code, "user-2", None)
|
||||
.await
|
||||
.unwrap_err();
|
||||
let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
|
||||
assert_eq!(invite_count, 1);
|
||||
}
|
||||
|
||||
pub struct TestDb {
|
||||
pub db: Option<Arc<dyn Db>>,
|
||||
pub url: String,
|
||||
@ -1290,7 +1633,12 @@ pub mod tests {
|
||||
|
||||
#[async_trait]
|
||||
impl Db for FakeDb {
|
||||
async fn create_user(&self, github_login: &str, admin: bool) -> Result<UserId> {
|
||||
async fn create_user(
|
||||
&self,
|
||||
github_login: &str,
|
||||
email_address: Option<&str>,
|
||||
admin: bool,
|
||||
) -> Result<UserId> {
|
||||
self.background.simulate_random_delay().await;
|
||||
|
||||
let mut users = self.users.lock();
|
||||
@ -1306,7 +1654,11 @@ pub mod tests {
|
||||
User {
|
||||
id: user_id,
|
||||
github_login: github_login.to_string(),
|
||||
email_address: email_address.map(str::to_string),
|
||||
admin,
|
||||
invite_code: None,
|
||||
invite_count: 0,
|
||||
connected_once: false,
|
||||
},
|
||||
);
|
||||
Ok(user_id)
|
||||
@ -1344,10 +1696,45 @@ pub mod tests {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
async fn set_user_connected_once(&self, id: UserId, connected_once: bool) -> Result<()> {
|
||||
self.background.simulate_random_delay().await;
|
||||
let mut users = self.users.lock();
|
||||
let mut user = users
|
||||
.get_mut(&id)
|
||||
.ok_or_else(|| anyhow!("user not found"))?;
|
||||
user.connected_once = connected_once;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn destroy_user(&self, _id: UserId) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
// invite codes
|
||||
|
||||
async fn set_invite_count(&self, _id: UserId, _count: u32) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
async fn get_invite_code_for_user(&self, _id: UserId) -> Result<Option<(String, u32)>> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
async fn get_user_for_invite_code(&self, _code: &str) -> Result<User> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
async fn redeem_invite_code(
|
||||
&self,
|
||||
_code: &str,
|
||||
_login: &str,
|
||||
_email_address: Option<&str>,
|
||||
) -> Result<UserId> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
// contacts
|
||||
|
||||
async fn get_contacts(&self, id: UserId) -> Result<Vec<Contact>> {
|
||||
self.background.simulate_random_delay().await;
|
||||
let mut contacts = vec![Contact::Accepted {
|
||||
@ -1457,7 +1844,7 @@ pub mod tests {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Err(anyhow!("no such notification"))
|
||||
Err(anyhow!("no such notification"))?
|
||||
}
|
||||
|
||||
async fn respond_to_contact_request(
|
||||
@ -1470,7 +1857,7 @@ pub mod tests {
|
||||
for (ix, contact) in contacts.iter_mut().enumerate() {
|
||||
if contact.requester_id == requester_id && contact.responder_id == responder_id {
|
||||
if contact.accepted {
|
||||
return Err(anyhow!("contact already confirmed"));
|
||||
Err(anyhow!("contact already confirmed"))?;
|
||||
}
|
||||
if accept {
|
||||
contact.accepted = true;
|
||||
@ -1481,7 +1868,7 @@ pub mod tests {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Err(anyhow!("no such contact request"))
|
||||
Err(anyhow!("no such contact request"))?
|
||||
}
|
||||
|
||||
async fn create_access_token_hash(
|
||||
@ -1505,7 +1892,7 @@ pub mod tests {
|
||||
self.background.simulate_random_delay().await;
|
||||
let mut orgs = self.orgs.lock();
|
||||
if orgs.values().any(|org| org.slug == slug) {
|
||||
Err(anyhow!("org already exists"))
|
||||
Err(anyhow!("org already exists"))?
|
||||
} else {
|
||||
let org_id = OrgId(post_inc(&mut *self.next_org_id.lock()));
|
||||
orgs.insert(
|
||||
@ -1528,10 +1915,10 @@ pub mod tests {
|
||||
) -> Result<()> {
|
||||
self.background.simulate_random_delay().await;
|
||||
if !self.orgs.lock().contains_key(&org_id) {
|
||||
return Err(anyhow!("org does not exist"));
|
||||
Err(anyhow!("org does not exist"))?;
|
||||
}
|
||||
if !self.users.lock().contains_key(&user_id) {
|
||||
return Err(anyhow!("user does not exist"));
|
||||
Err(anyhow!("user does not exist"))?;
|
||||
}
|
||||
|
||||
self.org_memberships
|
||||
@ -1544,7 +1931,7 @@ pub mod tests {
|
||||
async fn create_org_channel(&self, org_id: OrgId, name: &str) -> Result<ChannelId> {
|
||||
self.background.simulate_random_delay().await;
|
||||
if !self.orgs.lock().contains_key(&org_id) {
|
||||
return Err(anyhow!("org does not exist"));
|
||||
Err(anyhow!("org does not exist"))?;
|
||||
}
|
||||
|
||||
let mut channels = self.channels.lock();
|
||||
@ -1603,10 +1990,10 @@ pub mod tests {
|
||||
) -> Result<()> {
|
||||
self.background.simulate_random_delay().await;
|
||||
if !self.channels.lock().contains_key(&channel_id) {
|
||||
return Err(anyhow!("channel does not exist"));
|
||||
Err(anyhow!("channel does not exist"))?;
|
||||
}
|
||||
if !self.users.lock().contains_key(&user_id) {
|
||||
return Err(anyhow!("user does not exist"));
|
||||
Err(anyhow!("user does not exist"))?;
|
||||
}
|
||||
|
||||
self.channel_memberships
|
||||
@ -1626,10 +2013,10 @@ pub mod tests {
|
||||
) -> Result<MessageId> {
|
||||
self.background.simulate_random_delay().await;
|
||||
if !self.channels.lock().contains_key(&channel_id) {
|
||||
return Err(anyhow!("channel does not exist"));
|
||||
Err(anyhow!("channel does not exist"))?;
|
||||
}
|
||||
if !self.users.lock().contains_key(&sender_id) {
|
||||
return Err(anyhow!("user does not exist"));
|
||||
Err(anyhow!("user does not exist"))?;
|
||||
}
|
||||
|
||||
let mut messages = self.channel_messages.lock();
|
||||
|
@ -20,6 +20,7 @@ pub struct Config {
|
||||
pub http_port: u16,
|
||||
pub database_url: String,
|
||||
pub api_token: String,
|
||||
pub invite_link_prefix: String,
|
||||
pub honeycomb_api_key: Option<String>,
|
||||
pub honeycomb_dataset: Option<String>,
|
||||
pub rust_log: Option<String>,
|
||||
@ -29,6 +30,7 @@ pub struct Config {
|
||||
pub struct AppState {
|
||||
db: Arc<dyn Db>,
|
||||
api_token: String,
|
||||
invite_link_prefix: String,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
@ -37,6 +39,7 @@ impl AppState {
|
||||
let this = Self {
|
||||
db: Arc::new(db),
|
||||
api_token: config.api_token.clone(),
|
||||
invite_link_prefix: config.invite_link_prefix.clone(),
|
||||
};
|
||||
Ok(Arc::new(this))
|
||||
}
|
||||
@ -57,10 +60,11 @@ async fn main() -> Result<()> {
|
||||
|
||||
let listener = TcpListener::bind(&format!("0.0.0.0:{}", config.http_port))
|
||||
.expect("failed to bind TCP listener");
|
||||
let rpc_server = rpc::Server::new(state.clone(), None);
|
||||
|
||||
let app = Router::<Body>::new()
|
||||
.merge(api::routes(state.clone()))
|
||||
.merge(rpc::routes(state));
|
||||
.merge(api::routes(&rpc_server, state.clone()))
|
||||
.merge(rpc::routes(rpc_server));
|
||||
|
||||
axum::Server::from_tcp(listener)?
|
||||
.serve(app.into_make_service_with_connect_info::<SocketAddr>())
|
||||
@ -76,11 +80,26 @@ pub enum Error {
|
||||
Internal(anyhow::Error),
|
||||
}
|
||||
|
||||
impl<E> From<E> for Error
|
||||
where
|
||||
E: Into<anyhow::Error>,
|
||||
{
|
||||
fn from(error: E) -> Self {
|
||||
impl From<anyhow::Error> for Error {
|
||||
fn from(error: anyhow::Error) -> Self {
|
||||
Self::Internal(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<sqlx::Error> for Error {
|
||||
fn from(error: sqlx::Error) -> Self {
|
||||
Self::Internal(error.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<axum::Error> for Error {
|
||||
fn from(error: axum::Error) -> Self {
|
||||
Self::Internal(error.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<hyper::Error> for Error {
|
||||
fn from(error: hyper::Error) -> Self {
|
||||
Self::Internal(error.into())
|
||||
}
|
||||
}
|
||||
@ -114,6 +133,8 @@ impl std::fmt::Display for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
pub fn init_tracing(config: &Config) -> Option<()> {
|
||||
use opentelemetry::KeyValue;
|
||||
use opentelemetry_otlp::WithExportConfig;
|
||||
|
@ -23,7 +23,11 @@ use axum::{
|
||||
Extension, Router, TypedHeader,
|
||||
};
|
||||
use collections::HashMap;
|
||||
use futures::{channel::mpsc, future::BoxFuture, FutureExt, SinkExt, StreamExt, TryStreamExt};
|
||||
use futures::{
|
||||
channel::mpsc,
|
||||
future::{self, BoxFuture},
|
||||
FutureExt, SinkExt, StreamExt, TryStreamExt,
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
use rpc::{
|
||||
proto::{self, AnyTypedEnvelope, EntityMessage, EnvelopedMessage, RequestMessage},
|
||||
@ -276,12 +280,27 @@ impl Server {
|
||||
let _ = send_connection_id.send(connection_id).await;
|
||||
}
|
||||
|
||||
let contacts = this.app_state.db.get_contacts(user_id).await?;
|
||||
if !user.connected_once {
|
||||
this.peer.send(connection_id, proto::ShowContacts {})?;
|
||||
this.app_state.db.set_user_connected_once(user_id, true).await?;
|
||||
}
|
||||
|
||||
let (contacts, invite_code) = future::try_join(
|
||||
this.app_state.db.get_contacts(user_id),
|
||||
this.app_state.db.get_invite_code_for_user(user_id)
|
||||
).await?;
|
||||
|
||||
{
|
||||
let mut store = this.store_mut().await;
|
||||
store.add_connection(connection_id, user_id);
|
||||
this.peer.send(connection_id, store.build_initial_contacts_update(contacts))?;
|
||||
|
||||
if let Some((code, count)) = invite_code {
|
||||
this.peer.send(connection_id, proto::UpdateInviteInfo {
|
||||
url: format!("{}{}", this.app_state.invite_link_prefix, code),
|
||||
count,
|
||||
})?;
|
||||
}
|
||||
}
|
||||
this.update_user_contacts(user_id).await?;
|
||||
|
||||
@ -398,6 +417,23 @@ impl Server {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn invite_code_redeemed(self: &Arc<Self>, code: &str, invitee_id: UserId) -> Result<()> {
|
||||
let user = self.app_state.db.get_user_for_invite_code(code).await?;
|
||||
let store = self.store().await;
|
||||
let invitee_contact = store.contact_for_user(invitee_id, true);
|
||||
for connection_id in store.connection_ids_for_user(user.id) {
|
||||
self.peer.send(connection_id, proto::UpdateContacts {
|
||||
contacts: vec![invitee_contact.clone()],
|
||||
..Default::default()
|
||||
})?;
|
||||
self.peer.send(connection_id, proto::UpdateInviteInfo {
|
||||
url: format!("{}{}", self.app_state.invite_link_prefix, code),
|
||||
count: user.invite_count as u32,
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn ping(
|
||||
self: Arc<Server>,
|
||||
_: TypedEnvelope<proto::Ping>,
|
||||
@ -1529,13 +1565,12 @@ impl Header for ProtocolVersion {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn routes(app_state: Arc<AppState>) -> Router<Body> {
|
||||
let server = Server::new(app_state.clone(), None);
|
||||
pub fn routes(server: Arc<Server>) -> Router<Body> {
|
||||
Router::new()
|
||||
.route("/rpc", get(handle_websocket_request))
|
||||
.layer(
|
||||
ServiceBuilder::new()
|
||||
.layer(Extension(app_state))
|
||||
.layer(Extension(server.app_state.clone()))
|
||||
.layer(middleware::from_fn(auth::validate_header))
|
||||
.layer(Extension(server)),
|
||||
)
|
||||
@ -6039,9 +6074,9 @@ mod tests {
|
||||
|
||||
let mut server = TestServer::start(cx.foreground(), cx.background()).await;
|
||||
let db = server.app_state.db.clone();
|
||||
let host_user_id = db.create_user("host", false).await.unwrap();
|
||||
let host_user_id = db.create_user("host", None, false).await.unwrap();
|
||||
for username in ["guest-1", "guest-2", "guest-3", "guest-4"] {
|
||||
let guest_user_id = db.create_user(username, false).await.unwrap();
|
||||
let guest_user_id = db.create_user(username, None, false).await.unwrap();
|
||||
server
|
||||
.app_state
|
||||
.db
|
||||
@ -6556,7 +6591,7 @@ mod tests {
|
||||
if let Ok(Some(user)) = self.app_state.db.get_user_by_github_login(name).await {
|
||||
user.id
|
||||
} else {
|
||||
self.app_state.db.create_user(name, false).await.unwrap()
|
||||
self.app_state.db.create_user(name, None, false).await.unwrap()
|
||||
};
|
||||
let client_name = name.to_string();
|
||||
let mut client = Client::new(http.clone());
|
||||
@ -6687,6 +6722,7 @@ mod tests {
|
||||
Arc::new(AppState {
|
||||
db: test_db.db().clone(),
|
||||
api_token: Default::default(),
|
||||
invite_link_prefix: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@ settings = { path = "../settings" }
|
||||
theme = { path = "../theme" }
|
||||
util = { path = "../util" }
|
||||
workspace = { path = "../workspace" }
|
||||
anyhow = "1.0"
|
||||
futures = "0.3"
|
||||
log = "0.4"
|
||||
postage = { version = "0.4.1", features = ["futures-traits"] }
|
||||
|
@ -1,5 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::notifications::render_user_notification;
|
||||
use client::{ContactEvent, ContactEventKind, UserStore};
|
||||
use client::{ContactEventKind, User, UserStore};
|
||||
use gpui::{
|
||||
elements::*, impl_internal_actions, Entity, ModelHandle, MutableAppContext, RenderContext,
|
||||
View, ViewContext,
|
||||
@ -15,7 +17,8 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||
|
||||
pub struct ContactNotification {
|
||||
user_store: ModelHandle<UserStore>,
|
||||
event: ContactEvent,
|
||||
user: Arc<User>,
|
||||
kind: client::ContactEventKind,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -41,27 +44,27 @@ impl View for ContactNotification {
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
match self.event.kind {
|
||||
match self.kind {
|
||||
ContactEventKind::Requested => render_user_notification(
|
||||
self.event.user.clone(),
|
||||
self.user.clone(),
|
||||
"wants to add you as a contact",
|
||||
Some("They won't know if you decline."),
|
||||
RespondToContactRequest {
|
||||
user_id: self.event.user.id,
|
||||
user_id: self.user.id,
|
||||
accept: false,
|
||||
},
|
||||
vec![
|
||||
(
|
||||
"Decline",
|
||||
Box::new(RespondToContactRequest {
|
||||
user_id: self.event.user.id,
|
||||
user_id: self.user.id,
|
||||
accept: false,
|
||||
}),
|
||||
),
|
||||
(
|
||||
"Accept",
|
||||
Box::new(RespondToContactRequest {
|
||||
user_id: self.event.user.id,
|
||||
user_id: self.user.id,
|
||||
accept: true,
|
||||
}),
|
||||
),
|
||||
@ -69,10 +72,10 @@ impl View for ContactNotification {
|
||||
cx,
|
||||
),
|
||||
ContactEventKind::Accepted => render_user_notification(
|
||||
self.event.user.clone(),
|
||||
self.user.clone(),
|
||||
"accepted your contact request",
|
||||
None,
|
||||
Dismiss(self.event.user.id),
|
||||
Dismiss(self.user.id),
|
||||
vec![],
|
||||
cx,
|
||||
),
|
||||
@ -89,30 +92,35 @@ impl Notification for ContactNotification {
|
||||
|
||||
impl ContactNotification {
|
||||
pub fn new(
|
||||
event: ContactEvent,
|
||||
user: Arc<User>,
|
||||
kind: client::ContactEventKind,
|
||||
user_store: ModelHandle<UserStore>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
cx.subscribe(&user_store, move |this, _, event, cx| {
|
||||
if let client::ContactEvent {
|
||||
if let client::Event::Contact {
|
||||
kind: ContactEventKind::Cancelled,
|
||||
user,
|
||||
} = event
|
||||
{
|
||||
if user.id == this.event.user.id {
|
||||
if user.id == this.user.id {
|
||||
cx.emit(Event::Dismiss);
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
Self { event, user_store }
|
||||
Self {
|
||||
user,
|
||||
kind,
|
||||
user_store,
|
||||
}
|
||||
}
|
||||
|
||||
fn dismiss(&mut self, _: &Dismiss, cx: &mut ViewContext<Self>) {
|
||||
self.user_store.update(cx, |store, cx| {
|
||||
store
|
||||
.dismiss_contact_request(self.event.user.id, cx)
|
||||
.dismiss_contact_request(self.user.id, cx)
|
||||
.detach_and_log_err(cx);
|
||||
});
|
||||
cx.emit(Event::Dismiss);
|
||||
|
@ -12,8 +12,8 @@ use gpui::{
|
||||
geometry::{rect::RectF, vector::vec2f},
|
||||
impl_actions, impl_internal_actions,
|
||||
platform::CursorStyle,
|
||||
AppContext, Element, ElementBox, Entity, LayoutContext, ModelHandle, MutableAppContext,
|
||||
RenderContext, Subscription, View, ViewContext, ViewHandle, WeakViewHandle,
|
||||
AppContext, ClipboardItem, Element, ElementBox, Entity, LayoutContext, ModelHandle,
|
||||
MutableAppContext, RenderContext, Subscription, View, ViewContext, ViewHandle, WeakViewHandle,
|
||||
};
|
||||
use join_project_notification::JoinProjectNotification;
|
||||
use serde::Deserialize;
|
||||
@ -157,16 +157,28 @@ impl ContactsPanel {
|
||||
if let Some((workspace, user_store)) =
|
||||
workspace.upgrade(cx).zip(user_store.upgrade(cx))
|
||||
{
|
||||
workspace.update(cx, |workspace, cx| match event.kind {
|
||||
ContactEventKind::Requested | ContactEventKind::Accepted => workspace
|
||||
.show_notification(event.user.id as usize, cx, |cx| {
|
||||
cx.add_view(|cx| {
|
||||
ContactNotification::new(event.clone(), user_store, cx)
|
||||
})
|
||||
}),
|
||||
workspace.update(cx, |workspace, cx| match event {
|
||||
client::Event::Contact { user, kind } => match kind {
|
||||
ContactEventKind::Requested | ContactEventKind::Accepted => workspace
|
||||
.show_notification(user.id as usize, cx, |cx| {
|
||||
cx.add_view(|cx| {
|
||||
ContactNotification::new(
|
||||
user.clone(),
|
||||
*kind,
|
||||
user_store,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
}),
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
});
|
||||
}
|
||||
|
||||
if let client::Event::ShowContacts = event {
|
||||
cx.emit(Event::Activate);
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
@ -793,6 +805,10 @@ impl SidebarItem for ContactsPanel {
|
||||
fn contains_focused_view(&self, cx: &AppContext) -> bool {
|
||||
self.filter_editor.is_focused(cx)
|
||||
}
|
||||
|
||||
fn should_activate_item_on_event(&self, event: &Event, _: &AppContext) -> bool {
|
||||
matches!(event, Event::Activate)
|
||||
}
|
||||
}
|
||||
|
||||
fn render_icon_button(style: &IconButton, svg_path: &'static str) -> impl Element {
|
||||
@ -808,7 +824,9 @@ fn render_icon_button(style: &IconButton, svg_path: &'static str) -> impl Elemen
|
||||
.with_height(style.button_width)
|
||||
}
|
||||
|
||||
pub enum Event {}
|
||||
pub enum Event {
|
||||
Activate,
|
||||
}
|
||||
|
||||
impl Entity for ContactsPanel {
|
||||
type Event = Event;
|
||||
@ -855,6 +873,59 @@ impl View for ContactsPanel {
|
||||
.boxed(),
|
||||
)
|
||||
.with_child(List::new(self.list_state.clone()).flex(1., false).boxed())
|
||||
.with_children(
|
||||
self.user_store
|
||||
.read(cx)
|
||||
.invite_info()
|
||||
.cloned()
|
||||
.and_then(|info| {
|
||||
enum InviteLink {}
|
||||
|
||||
if info.count > 0 {
|
||||
Some(
|
||||
MouseEventHandler::new::<InviteLink, _, _>(
|
||||
0,
|
||||
cx,
|
||||
|state, cx| {
|
||||
let style =
|
||||
theme.invite_row.style_for(state, false).clone();
|
||||
|
||||
let copied =
|
||||
cx.read_from_clipboard().map_or(false, |item| {
|
||||
item.text().as_str() == info.url.as_ref()
|
||||
});
|
||||
|
||||
Label::new(
|
||||
format!(
|
||||
"{} invite link ({} left)",
|
||||
if copied { "Copied" } else { "Copy" },
|
||||
info.count
|
||||
),
|
||||
style.label.clone(),
|
||||
)
|
||||
.aligned()
|
||||
.left()
|
||||
.constrained()
|
||||
.with_height(theme.row_height)
|
||||
.contained()
|
||||
.with_style(style.container)
|
||||
.boxed()
|
||||
},
|
||||
)
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(move |_, cx| {
|
||||
cx.write_to_clipboard(ClipboardItem::new(
|
||||
info.url.to_string(),
|
||||
));
|
||||
cx.notify();
|
||||
})
|
||||
.boxed(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}),
|
||||
)
|
||||
.boxed(),
|
||||
)
|
||||
.with_style(theme.container)
|
||||
|
@ -87,18 +87,20 @@ message Envelope {
|
||||
GetChannelMessagesResponse get_channel_messages_response = 75;
|
||||
|
||||
UpdateContacts update_contacts = 76;
|
||||
UpdateInviteInfo update_invite_info = 77;
|
||||
ShowContacts show_contacts = 78;
|
||||
|
||||
GetUsers get_users = 77;
|
||||
FuzzySearchUsers fuzzy_search_users = 78;
|
||||
UsersResponse users_response = 79;
|
||||
RequestContact request_contact = 80;
|
||||
RespondToContactRequest respond_to_contact_request = 81;
|
||||
RemoveContact remove_contact = 82;
|
||||
GetUsers get_users = 79;
|
||||
FuzzySearchUsers fuzzy_search_users = 80;
|
||||
UsersResponse users_response = 81;
|
||||
RequestContact request_contact = 82;
|
||||
RespondToContactRequest respond_to_contact_request = 83;
|
||||
RemoveContact remove_contact = 84;
|
||||
|
||||
Follow follow = 83;
|
||||
FollowResponse follow_response = 84;
|
||||
UpdateFollowers update_followers = 85;
|
||||
Unfollow unfollow = 86;
|
||||
Follow follow = 85;
|
||||
FollowResponse follow_response = 86;
|
||||
UpdateFollowers update_followers = 87;
|
||||
Unfollow unfollow = 88;
|
||||
}
|
||||
}
|
||||
|
||||
@ -634,6 +636,13 @@ message UpdateContacts {
|
||||
repeated uint64 remove_outgoing_requests = 6;
|
||||
}
|
||||
|
||||
message UpdateInviteInfo {
|
||||
string url = 1;
|
||||
uint32 count = 2;
|
||||
}
|
||||
|
||||
message ShowContacts {}
|
||||
|
||||
message IncomingContactRequest {
|
||||
uint64 requester_id = 1;
|
||||
bool should_notify = 2;
|
||||
|
@ -145,6 +145,7 @@ messages!(
|
||||
(SearchProjectResponse, Background),
|
||||
(SendChannelMessage, Foreground),
|
||||
(SendChannelMessageResponse, Foreground),
|
||||
(ShowContacts, Foreground),
|
||||
(StartLanguageServer, Foreground),
|
||||
(Test, Foreground),
|
||||
(Unfollow, Foreground),
|
||||
@ -155,6 +156,7 @@ messages!(
|
||||
(UpdateContacts, Foreground),
|
||||
(UpdateDiagnosticSummary, Foreground),
|
||||
(UpdateFollowers, Foreground),
|
||||
(UpdateInviteInfo, Foreground),
|
||||
(UpdateLanguageServer, Foreground),
|
||||
(UpdateWorktree, Foreground),
|
||||
);
|
||||
|
@ -6,4 +6,4 @@ pub use conn::Connection;
|
||||
pub use peer::*;
|
||||
mod macros;
|
||||
|
||||
pub const PROTOCOL_VERSION: u32 = 18;
|
||||
pub const PROTOCOL_VERSION: u32 = 19;
|
||||
|
@ -262,6 +262,16 @@ pub struct ContactsPanel {
|
||||
pub disabled_contact_button: IconButton,
|
||||
pub tree_branch: Interactive<TreeBranch>,
|
||||
pub section_icon_size: f32,
|
||||
pub invite_row: Interactive<ContainedLabel>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default)]
|
||||
pub struct InviteLink {
|
||||
#[serde(flatten)]
|
||||
pub container: ContainerStyle,
|
||||
#[serde(flatten)]
|
||||
pub label: LabelStyle,
|
||||
pub icon: Icon,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default, Clone, Copy)]
|
||||
@ -279,6 +289,15 @@ pub struct ContactFinder {
|
||||
pub disabled_contact_button: IconButton,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default)]
|
||||
pub struct Icon {
|
||||
#[serde(flatten)]
|
||||
pub container: ContainerStyle,
|
||||
pub color: Color,
|
||||
pub width: f32,
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default)]
|
||||
pub struct IconButton {
|
||||
#[serde(flatten)]
|
||||
|
@ -9,6 +9,9 @@ use std::{cell::RefCell, rc::Rc};
|
||||
use theme::Theme;
|
||||
|
||||
pub trait SidebarItem: View {
|
||||
fn should_activate_item_on_event(&self, _: &Self::Event, _: &AppContext) -> bool {
|
||||
false
|
||||
}
|
||||
fn should_show_badge(&self, cx: &AppContext) -> bool;
|
||||
fn contains_focused_view(&self, _: &AppContext) -> bool {
|
||||
false
|
||||
@ -16,6 +19,7 @@ pub trait SidebarItem: View {
|
||||
}
|
||||
|
||||
pub trait SidebarItemHandle {
|
||||
fn id(&self) -> usize;
|
||||
fn should_show_badge(&self, cx: &AppContext) -> bool;
|
||||
fn is_focused(&self, cx: &AppContext) -> bool;
|
||||
fn to_any(&self) -> AnyViewHandle;
|
||||
@ -25,6 +29,10 @@ impl<T> SidebarItemHandle for ViewHandle<T>
|
||||
where
|
||||
T: SidebarItem,
|
||||
{
|
||||
fn id(&self) -> usize {
|
||||
self.id()
|
||||
}
|
||||
|
||||
fn should_show_badge(&self, cx: &AppContext) -> bool {
|
||||
self.read(cx).should_show_badge(cx)
|
||||
}
|
||||
@ -61,7 +69,7 @@ pub enum Side {
|
||||
struct Item {
|
||||
icon_path: &'static str,
|
||||
view: Rc<dyn SidebarItemHandle>,
|
||||
_observation: Subscription,
|
||||
_subscriptions: [Subscription; 2],
|
||||
}
|
||||
|
||||
pub struct SidebarButtons {
|
||||
@ -99,11 +107,24 @@ impl Sidebar {
|
||||
view: ViewHandle<T>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let subscription = cx.observe(&view, |_, _, cx| cx.notify());
|
||||
let subscriptions = [
|
||||
cx.observe(&view, |_, _, cx| cx.notify()),
|
||||
cx.subscribe(&view, |this, view, event, cx| {
|
||||
if view.read(cx).should_activate_item_on_event(event, cx) {
|
||||
if let Some(ix) = this
|
||||
.items
|
||||
.iter()
|
||||
.position(|item| item.view.id() == view.id())
|
||||
{
|
||||
this.activate_item(ix, cx);
|
||||
}
|
||||
}
|
||||
}),
|
||||
];
|
||||
self.items.push(Item {
|
||||
icon_path,
|
||||
view: Rc::new(view),
|
||||
_observation: subscription,
|
||||
_subscriptions: subscriptions,
|
||||
});
|
||||
cx.notify()
|
||||
}
|
||||
|
519
styles/dist/cave-dark.json
vendored
Normal file
519
styles/dist/cave-dark.json
vendored
Normal file
@ -0,0 +1,519 @@
|
||||
{
|
||||
"meta": {
|
||||
"themeName": "cave-dark"
|
||||
},
|
||||
"text": {
|
||||
"primary": {
|
||||
"value": "#e2dfe7",
|
||||
"type": "color"
|
||||
},
|
||||
"secondary": {
|
||||
"value": "#8b8792",
|
||||
"type": "color"
|
||||
},
|
||||
"muted": {
|
||||
"value": "#8b8792",
|
||||
"type": "color"
|
||||
},
|
||||
"placeholder": {
|
||||
"value": "#7e7887",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#efecf4",
|
||||
"type": "color"
|
||||
},
|
||||
"feature": {
|
||||
"value": "#576ddb",
|
||||
"type": "color"
|
||||
},
|
||||
"ok": {
|
||||
"value": "#2a9292",
|
||||
"type": "color"
|
||||
},
|
||||
"error": {
|
||||
"value": "#be4678",
|
||||
"type": "color"
|
||||
},
|
||||
"warning": {
|
||||
"value": "#a06e3b",
|
||||
"type": "color"
|
||||
},
|
||||
"info": {
|
||||
"value": "#576ddb",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"icon": {
|
||||
"primary": {
|
||||
"value": "#e2dfe7",
|
||||
"type": "color"
|
||||
},
|
||||
"secondary": {
|
||||
"value": "#8b8792",
|
||||
"type": "color"
|
||||
},
|
||||
"muted": {
|
||||
"value": "#8b8792",
|
||||
"type": "color"
|
||||
},
|
||||
"placeholder": {
|
||||
"value": "#7e7887",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#efecf4",
|
||||
"type": "color"
|
||||
},
|
||||
"feature": {
|
||||
"value": "#576ddb",
|
||||
"type": "color"
|
||||
},
|
||||
"ok": {
|
||||
"value": "#2a9292",
|
||||
"type": "color"
|
||||
},
|
||||
"error": {
|
||||
"value": "#be4678",
|
||||
"type": "color"
|
||||
},
|
||||
"warning": {
|
||||
"value": "#a06e3b",
|
||||
"type": "color"
|
||||
},
|
||||
"info": {
|
||||
"value": "#576ddb",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"background": {
|
||||
"100": {
|
||||
"base": {
|
||||
"value": "#332f38",
|
||||
"type": "color"
|
||||
},
|
||||
"hovered": {
|
||||
"value": "#3f3b45",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#4c4653",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"300": {
|
||||
"base": {
|
||||
"value": "#26232a",
|
||||
"type": "color"
|
||||
},
|
||||
"hovered": {
|
||||
"value": "#332f38",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#3f3b45",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"base": {
|
||||
"value": "#19171c",
|
||||
"type": "color"
|
||||
},
|
||||
"hovered": {
|
||||
"value": "#1c1a20",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#201d23",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"on300": {
|
||||
"base": {
|
||||
"value": "#19171c",
|
||||
"type": "color"
|
||||
},
|
||||
"hovered": {
|
||||
"value": "#1c1a20",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#201d23",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"on500": {
|
||||
"base": {
|
||||
"value": "#332f38",
|
||||
"type": "color"
|
||||
},
|
||||
"hovered": {
|
||||
"value": "#3f3b45",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#4c4653",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"ok": {
|
||||
"base": {
|
||||
"value": "#2a929226",
|
||||
"type": "color"
|
||||
},
|
||||
"hovered": {
|
||||
"value": "#2a929233",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#2a929240",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"base": {
|
||||
"value": "#be467826",
|
||||
"type": "color"
|
||||
},
|
||||
"hovered": {
|
||||
"value": "#be467833",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#be467840",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"warning": {
|
||||
"base": {
|
||||
"value": "#a06e3b26",
|
||||
"type": "color"
|
||||
},
|
||||
"hovered": {
|
||||
"value": "#a06e3b33",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#a06e3b40",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"info": {
|
||||
"base": {
|
||||
"value": "#576ddb26",
|
||||
"type": "color"
|
||||
},
|
||||
"hovered": {
|
||||
"value": "#576ddb33",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#576ddb40",
|
||||
"type": "color"
|
||||
}
|
||||
}
|
||||
},
|
||||
"border": {
|
||||
"primary": {
|
||||
"value": "#19171c",
|
||||
"type": "color"
|
||||
},
|
||||
"secondary": {
|
||||
"value": "#26232a",
|
||||
"type": "color"
|
||||
},
|
||||
"muted": {
|
||||
"value": "#655f6d",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#655f6d",
|
||||
"type": "color"
|
||||
},
|
||||
"onMedia": {
|
||||
"value": "#19171c1a",
|
||||
"type": "color"
|
||||
},
|
||||
"ok": {
|
||||
"value": "#2a929226",
|
||||
"type": "color"
|
||||
},
|
||||
"error": {
|
||||
"value": "#be467826",
|
||||
"type": "color"
|
||||
},
|
||||
"warning": {
|
||||
"value": "#a06e3b26",
|
||||
"type": "color"
|
||||
},
|
||||
"info": {
|
||||
"value": "#576ddb26",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"editor": {
|
||||
"background": {
|
||||
"value": "#19171c",
|
||||
"type": "color"
|
||||
},
|
||||
"indent_guide": {
|
||||
"value": "#655f6d",
|
||||
"type": "color"
|
||||
},
|
||||
"indent_guide_active": {
|
||||
"value": "#26232a",
|
||||
"type": "color"
|
||||
},
|
||||
"line": {
|
||||
"active": {
|
||||
"value": "#26232a",
|
||||
"type": "color"
|
||||
},
|
||||
"highlighted": {
|
||||
"value": "#332f38",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"highlight": {
|
||||
"selection": {
|
||||
"value": "#576ddb3d",
|
||||
"type": "color"
|
||||
},
|
||||
"occurrence": {
|
||||
"value": "#5852603d",
|
||||
"type": "color"
|
||||
},
|
||||
"activeOccurrence": {
|
||||
"value": "#5852607a",
|
||||
"type": "color"
|
||||
},
|
||||
"matchingBracket": {
|
||||
"value": "#201d23",
|
||||
"type": "color"
|
||||
},
|
||||
"match": {
|
||||
"value": "#3d1576",
|
||||
"type": "color"
|
||||
},
|
||||
"activeMatch": {
|
||||
"value": "#782edf7a",
|
||||
"type": "color"
|
||||
},
|
||||
"related": {
|
||||
"value": "#1c1a20",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"gutter": {
|
||||
"primary": {
|
||||
"value": "#7e7887",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#efecf4",
|
||||
"type": "color"
|
||||
}
|
||||
}
|
||||
},
|
||||
"syntax": {
|
||||
"primary": {
|
||||
"value": "#efecf4",
|
||||
"type": "color"
|
||||
},
|
||||
"comment": {
|
||||
"value": "#8b8792",
|
||||
"type": "color"
|
||||
},
|
||||
"keyword": {
|
||||
"value": "#576ddb",
|
||||
"type": "color"
|
||||
},
|
||||
"function": {
|
||||
"value": "#a06e3b",
|
||||
"type": "color"
|
||||
},
|
||||
"type": {
|
||||
"value": "#398bc6",
|
||||
"type": "color"
|
||||
},
|
||||
"variant": {
|
||||
"value": "#576ddb",
|
||||
"type": "color"
|
||||
},
|
||||
"property": {
|
||||
"value": "#576ddb",
|
||||
"type": "color"
|
||||
},
|
||||
"enum": {
|
||||
"value": "#aa573c",
|
||||
"type": "color"
|
||||
},
|
||||
"operator": {
|
||||
"value": "#aa573c",
|
||||
"type": "color"
|
||||
},
|
||||
"string": {
|
||||
"value": "#aa573c",
|
||||
"type": "color"
|
||||
},
|
||||
"number": {
|
||||
"value": "#2a9292",
|
||||
"type": "color"
|
||||
},
|
||||
"boolean": {
|
||||
"value": "#2a9292",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"player": {
|
||||
"1": {
|
||||
"baseColor": {
|
||||
"value": "#576ddb",
|
||||
"type": "color"
|
||||
},
|
||||
"cursorColor": {
|
||||
"value": "#576ddb",
|
||||
"type": "color"
|
||||
},
|
||||
"selectionColor": {
|
||||
"value": "#576ddb3d",
|
||||
"type": "color"
|
||||
},
|
||||
"borderColor": {
|
||||
"value": "#576ddbcc",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"2": {
|
||||
"baseColor": {
|
||||
"value": "#2a9292",
|
||||
"type": "color"
|
||||
},
|
||||
"cursorColor": {
|
||||
"value": "#2a9292",
|
||||
"type": "color"
|
||||
},
|
||||
"selectionColor": {
|
||||
"value": "#2a92923d",
|
||||
"type": "color"
|
||||
},
|
||||
"borderColor": {
|
||||
"value": "#2a9292cc",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"3": {
|
||||
"baseColor": {
|
||||
"value": "#bf40bf",
|
||||
"type": "color"
|
||||
},
|
||||
"cursorColor": {
|
||||
"value": "#bf40bf",
|
||||
"type": "color"
|
||||
},
|
||||
"selectionColor": {
|
||||
"value": "#bf40bf3d",
|
||||
"type": "color"
|
||||
},
|
||||
"borderColor": {
|
||||
"value": "#bf40bfcc",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"4": {
|
||||
"baseColor": {
|
||||
"value": "#aa573c",
|
||||
"type": "color"
|
||||
},
|
||||
"cursorColor": {
|
||||
"value": "#aa573c",
|
||||
"type": "color"
|
||||
},
|
||||
"selectionColor": {
|
||||
"value": "#aa573c3d",
|
||||
"type": "color"
|
||||
},
|
||||
"borderColor": {
|
||||
"value": "#aa573ccc",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"5": {
|
||||
"baseColor": {
|
||||
"value": "#955ae7",
|
||||
"type": "color"
|
||||
},
|
||||
"cursorColor": {
|
||||
"value": "#955ae7",
|
||||
"type": "color"
|
||||
},
|
||||
"selectionColor": {
|
||||
"value": "#955ae73d",
|
||||
"type": "color"
|
||||
},
|
||||
"borderColor": {
|
||||
"value": "#955ae7cc",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"6": {
|
||||
"baseColor": {
|
||||
"value": "#398bc6",
|
||||
"type": "color"
|
||||
},
|
||||
"cursorColor": {
|
||||
"value": "#398bc6",
|
||||
"type": "color"
|
||||
},
|
||||
"selectionColor": {
|
||||
"value": "#398bc63d",
|
||||
"type": "color"
|
||||
},
|
||||
"borderColor": {
|
||||
"value": "#398bc6cc",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"7": {
|
||||
"baseColor": {
|
||||
"value": "#be4678",
|
||||
"type": "color"
|
||||
},
|
||||
"cursorColor": {
|
||||
"value": "#be4678",
|
||||
"type": "color"
|
||||
},
|
||||
"selectionColor": {
|
||||
"value": "#be46783d",
|
||||
"type": "color"
|
||||
},
|
||||
"borderColor": {
|
||||
"value": "#be4678cc",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"8": {
|
||||
"baseColor": {
|
||||
"value": "#a06e3b",
|
||||
"type": "color"
|
||||
},
|
||||
"cursorColor": {
|
||||
"value": "#a06e3b",
|
||||
"type": "color"
|
||||
},
|
||||
"selectionColor": {
|
||||
"value": "#a06e3b3d",
|
||||
"type": "color"
|
||||
},
|
||||
"borderColor": {
|
||||
"value": "#a06e3bcc",
|
||||
"type": "color"
|
||||
}
|
||||
}
|
||||
},
|
||||
"shadowAlpha": {
|
||||
"value": 0.24,
|
||||
"type": "number"
|
||||
}
|
||||
}
|
519
styles/dist/cave-light.json
vendored
Normal file
519
styles/dist/cave-light.json
vendored
Normal file
@ -0,0 +1,519 @@
|
||||
{
|
||||
"meta": {
|
||||
"themeName": "cave-light"
|
||||
},
|
||||
"text": {
|
||||
"primary": {
|
||||
"value": "#26232a",
|
||||
"type": "color"
|
||||
},
|
||||
"secondary": {
|
||||
"value": "#585260",
|
||||
"type": "color"
|
||||
},
|
||||
"muted": {
|
||||
"value": "#585260",
|
||||
"type": "color"
|
||||
},
|
||||
"placeholder": {
|
||||
"value": "#655f6d",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#19171c",
|
||||
"type": "color"
|
||||
},
|
||||
"feature": {
|
||||
"value": "#576ddb",
|
||||
"type": "color"
|
||||
},
|
||||
"ok": {
|
||||
"value": "#2a9292",
|
||||
"type": "color"
|
||||
},
|
||||
"error": {
|
||||
"value": "#be4678",
|
||||
"type": "color"
|
||||
},
|
||||
"warning": {
|
||||
"value": "#a06e3b",
|
||||
"type": "color"
|
||||
},
|
||||
"info": {
|
||||
"value": "#576ddb",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"icon": {
|
||||
"primary": {
|
||||
"value": "#26232a",
|
||||
"type": "color"
|
||||
},
|
||||
"secondary": {
|
||||
"value": "#585260",
|
||||
"type": "color"
|
||||
},
|
||||
"muted": {
|
||||
"value": "#585260",
|
||||
"type": "color"
|
||||
},
|
||||
"placeholder": {
|
||||
"value": "#655f6d",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#19171c",
|
||||
"type": "color"
|
||||
},
|
||||
"feature": {
|
||||
"value": "#576ddb",
|
||||
"type": "color"
|
||||
},
|
||||
"ok": {
|
||||
"value": "#2a9292",
|
||||
"type": "color"
|
||||
},
|
||||
"error": {
|
||||
"value": "#be4678",
|
||||
"type": "color"
|
||||
},
|
||||
"warning": {
|
||||
"value": "#a06e3b",
|
||||
"type": "color"
|
||||
},
|
||||
"info": {
|
||||
"value": "#576ddb",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"background": {
|
||||
"100": {
|
||||
"base": {
|
||||
"value": "#ccc9d2",
|
||||
"type": "color"
|
||||
},
|
||||
"hovered": {
|
||||
"value": "#b7b3bd",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#a19da7",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"300": {
|
||||
"base": {
|
||||
"value": "#e2dfe7",
|
||||
"type": "color"
|
||||
},
|
||||
"hovered": {
|
||||
"value": "#ccc9d2",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#b7b3bd",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"base": {
|
||||
"value": "#efecf4",
|
||||
"type": "color"
|
||||
},
|
||||
"hovered": {
|
||||
"value": "#ece9f1",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#e9e6ee",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"on300": {
|
||||
"base": {
|
||||
"value": "#efecf4",
|
||||
"type": "color"
|
||||
},
|
||||
"hovered": {
|
||||
"value": "#ece9f1",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#e9e6ee",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"on500": {
|
||||
"base": {
|
||||
"value": "#ccc9d2",
|
||||
"type": "color"
|
||||
},
|
||||
"hovered": {
|
||||
"value": "#b7b3bd",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#a19da7",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"ok": {
|
||||
"base": {
|
||||
"value": "#2a929226",
|
||||
"type": "color"
|
||||
},
|
||||
"hovered": {
|
||||
"value": "#2a929233",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#2a929240",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"base": {
|
||||
"value": "#be467826",
|
||||
"type": "color"
|
||||
},
|
||||
"hovered": {
|
||||
"value": "#be467833",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#be467840",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"warning": {
|
||||
"base": {
|
||||
"value": "#a06e3b26",
|
||||
"type": "color"
|
||||
},
|
||||
"hovered": {
|
||||
"value": "#a06e3b33",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#a06e3b40",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"info": {
|
||||
"base": {
|
||||
"value": "#576ddb26",
|
||||
"type": "color"
|
||||
},
|
||||
"hovered": {
|
||||
"value": "#576ddb33",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#576ddb40",
|
||||
"type": "color"
|
||||
}
|
||||
}
|
||||
},
|
||||
"border": {
|
||||
"primary": {
|
||||
"value": "#b7b3bd",
|
||||
"type": "color"
|
||||
},
|
||||
"secondary": {
|
||||
"value": "#ccc9d2",
|
||||
"type": "color"
|
||||
},
|
||||
"muted": {
|
||||
"value": "#e2dfe7",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#655f6d",
|
||||
"type": "color"
|
||||
},
|
||||
"onMedia": {
|
||||
"value": "#efecf41a",
|
||||
"type": "color"
|
||||
},
|
||||
"ok": {
|
||||
"value": "#2a929226",
|
||||
"type": "color"
|
||||
},
|
||||
"error": {
|
||||
"value": "#be467826",
|
||||
"type": "color"
|
||||
},
|
||||
"warning": {
|
||||
"value": "#a06e3b26",
|
||||
"type": "color"
|
||||
},
|
||||
"info": {
|
||||
"value": "#576ddb26",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"editor": {
|
||||
"background": {
|
||||
"value": "#efecf4",
|
||||
"type": "color"
|
||||
},
|
||||
"indent_guide": {
|
||||
"value": "#e2dfe7",
|
||||
"type": "color"
|
||||
},
|
||||
"indent_guide_active": {
|
||||
"value": "#ccc9d2",
|
||||
"type": "color"
|
||||
},
|
||||
"line": {
|
||||
"active": {
|
||||
"value": "#e2dfe7",
|
||||
"type": "color"
|
||||
},
|
||||
"highlighted": {
|
||||
"value": "#ccc9d2",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"highlight": {
|
||||
"selection": {
|
||||
"value": "#576ddb3d",
|
||||
"type": "color"
|
||||
},
|
||||
"occurrence": {
|
||||
"value": "#8b87921f",
|
||||
"type": "color"
|
||||
},
|
||||
"activeOccurrence": {
|
||||
"value": "#8b87923d",
|
||||
"type": "color"
|
||||
},
|
||||
"matchingBracket": {
|
||||
"value": "#e9e6ee",
|
||||
"type": "color"
|
||||
},
|
||||
"match": {
|
||||
"value": "#d5bdfa",
|
||||
"type": "color"
|
||||
},
|
||||
"activeMatch": {
|
||||
"value": "#a775ee3d",
|
||||
"type": "color"
|
||||
},
|
||||
"related": {
|
||||
"value": "#ece9f1",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"gutter": {
|
||||
"primary": {
|
||||
"value": "#655f6d",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#19171c",
|
||||
"type": "color"
|
||||
}
|
||||
}
|
||||
},
|
||||
"syntax": {
|
||||
"primary": {
|
||||
"value": "#19171c",
|
||||
"type": "color"
|
||||
},
|
||||
"comment": {
|
||||
"value": "#585260",
|
||||
"type": "color"
|
||||
},
|
||||
"keyword": {
|
||||
"value": "#576ddb",
|
||||
"type": "color"
|
||||
},
|
||||
"function": {
|
||||
"value": "#a06e3b",
|
||||
"type": "color"
|
||||
},
|
||||
"type": {
|
||||
"value": "#398bc6",
|
||||
"type": "color"
|
||||
},
|
||||
"variant": {
|
||||
"value": "#576ddb",
|
||||
"type": "color"
|
||||
},
|
||||
"property": {
|
||||
"value": "#576ddb",
|
||||
"type": "color"
|
||||
},
|
||||
"enum": {
|
||||
"value": "#aa573c",
|
||||
"type": "color"
|
||||
},
|
||||
"operator": {
|
||||
"value": "#aa573c",
|
||||
"type": "color"
|
||||
},
|
||||
"string": {
|
||||
"value": "#aa573c",
|
||||
"type": "color"
|
||||
},
|
||||
"number": {
|
||||
"value": "#2a9292",
|
||||
"type": "color"
|
||||
},
|
||||
"boolean": {
|
||||
"value": "#2a9292",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"player": {
|
||||
"1": {
|
||||
"baseColor": {
|
||||
"value": "#576ddb",
|
||||
"type": "color"
|
||||
},
|
||||
"cursorColor": {
|
||||
"value": "#576ddb",
|
||||
"type": "color"
|
||||
},
|
||||
"selectionColor": {
|
||||
"value": "#576ddb3d",
|
||||
"type": "color"
|
||||
},
|
||||
"borderColor": {
|
||||
"value": "#576ddbcc",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"2": {
|
||||
"baseColor": {
|
||||
"value": "#2a9292",
|
||||
"type": "color"
|
||||
},
|
||||
"cursorColor": {
|
||||
"value": "#2a9292",
|
||||
"type": "color"
|
||||
},
|
||||
"selectionColor": {
|
||||
"value": "#2a92923d",
|
||||
"type": "color"
|
||||
},
|
||||
"borderColor": {
|
||||
"value": "#2a9292cc",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"3": {
|
||||
"baseColor": {
|
||||
"value": "#bf40bf",
|
||||
"type": "color"
|
||||
},
|
||||
"cursorColor": {
|
||||
"value": "#bf40bf",
|
||||
"type": "color"
|
||||
},
|
||||
"selectionColor": {
|
||||
"value": "#bf40bf3d",
|
||||
"type": "color"
|
||||
},
|
||||
"borderColor": {
|
||||
"value": "#bf40bfcc",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"4": {
|
||||
"baseColor": {
|
||||
"value": "#aa573c",
|
||||
"type": "color"
|
||||
},
|
||||
"cursorColor": {
|
||||
"value": "#aa573c",
|
||||
"type": "color"
|
||||
},
|
||||
"selectionColor": {
|
||||
"value": "#aa573c3d",
|
||||
"type": "color"
|
||||
},
|
||||
"borderColor": {
|
||||
"value": "#aa573ccc",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"5": {
|
||||
"baseColor": {
|
||||
"value": "#955ae7",
|
||||
"type": "color"
|
||||
},
|
||||
"cursorColor": {
|
||||
"value": "#955ae7",
|
||||
"type": "color"
|
||||
},
|
||||
"selectionColor": {
|
||||
"value": "#955ae73d",
|
||||
"type": "color"
|
||||
},
|
||||
"borderColor": {
|
||||
"value": "#955ae7cc",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"6": {
|
||||
"baseColor": {
|
||||
"value": "#398bc6",
|
||||
"type": "color"
|
||||
},
|
||||
"cursorColor": {
|
||||
"value": "#398bc6",
|
||||
"type": "color"
|
||||
},
|
||||
"selectionColor": {
|
||||
"value": "#398bc63d",
|
||||
"type": "color"
|
||||
},
|
||||
"borderColor": {
|
||||
"value": "#398bc6cc",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"7": {
|
||||
"baseColor": {
|
||||
"value": "#be4678",
|
||||
"type": "color"
|
||||
},
|
||||
"cursorColor": {
|
||||
"value": "#be4678",
|
||||
"type": "color"
|
||||
},
|
||||
"selectionColor": {
|
||||
"value": "#be46783d",
|
||||
"type": "color"
|
||||
},
|
||||
"borderColor": {
|
||||
"value": "#be4678cc",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"8": {
|
||||
"baseColor": {
|
||||
"value": "#a06e3b",
|
||||
"type": "color"
|
||||
},
|
||||
"cursorColor": {
|
||||
"value": "#a06e3b",
|
||||
"type": "color"
|
||||
},
|
||||
"selectionColor": {
|
||||
"value": "#a06e3b3d",
|
||||
"type": "color"
|
||||
},
|
||||
"borderColor": {
|
||||
"value": "#a06e3bcc",
|
||||
"type": "color"
|
||||
}
|
||||
}
|
||||
},
|
||||
"shadowAlpha": {
|
||||
"value": 0.12,
|
||||
"type": "number"
|
||||
}
|
||||
}
|
10
styles/dist/solarized-light.json
vendored
10
styles/dist/solarized-light.json
vendored
@ -216,15 +216,15 @@
|
||||
},
|
||||
"border": {
|
||||
"primary": {
|
||||
"value": "#93a1a1",
|
||||
"value": "#c1c5bb",
|
||||
"type": "color"
|
||||
},
|
||||
"secondary": {
|
||||
"value": "#93a1a1",
|
||||
"value": "#d7d6c8",
|
||||
"type": "color"
|
||||
},
|
||||
"muted": {
|
||||
"value": "#657b83",
|
||||
"value": "#eee8d5",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
@ -258,11 +258,11 @@
|
||||
"type": "color"
|
||||
},
|
||||
"indent_guide": {
|
||||
"value": "#657b83",
|
||||
"value": "#eee8d5",
|
||||
"type": "color"
|
||||
},
|
||||
"indent_guide_active": {
|
||||
"value": "#93a1a1",
|
||||
"value": "#d7d6c8",
|
||||
"type": "color"
|
||||
},
|
||||
"line": {
|
||||
|
519
styles/dist/sulphurpool-dark.json
vendored
Normal file
519
styles/dist/sulphurpool-dark.json
vendored
Normal file
@ -0,0 +1,519 @@
|
||||
{
|
||||
"meta": {
|
||||
"themeName": "sulphurpool-dark"
|
||||
},
|
||||
"text": {
|
||||
"primary": {
|
||||
"value": "#dfe2f1",
|
||||
"type": "color"
|
||||
},
|
||||
"secondary": {
|
||||
"value": "#979db4",
|
||||
"type": "color"
|
||||
},
|
||||
"muted": {
|
||||
"value": "#979db4",
|
||||
"type": "color"
|
||||
},
|
||||
"placeholder": {
|
||||
"value": "#898ea4",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#f5f7ff",
|
||||
"type": "color"
|
||||
},
|
||||
"feature": {
|
||||
"value": "#3d8fd1",
|
||||
"type": "color"
|
||||
},
|
||||
"ok": {
|
||||
"value": "#ac9739",
|
||||
"type": "color"
|
||||
},
|
||||
"error": {
|
||||
"value": "#c94922",
|
||||
"type": "color"
|
||||
},
|
||||
"warning": {
|
||||
"value": "#c08b30",
|
||||
"type": "color"
|
||||
},
|
||||
"info": {
|
||||
"value": "#3d8fd1",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"icon": {
|
||||
"primary": {
|
||||
"value": "#dfe2f1",
|
||||
"type": "color"
|
||||
},
|
||||
"secondary": {
|
||||
"value": "#979db4",
|
||||
"type": "color"
|
||||
},
|
||||
"muted": {
|
||||
"value": "#979db4",
|
||||
"type": "color"
|
||||
},
|
||||
"placeholder": {
|
||||
"value": "#898ea4",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#f5f7ff",
|
||||
"type": "color"
|
||||
},
|
||||
"feature": {
|
||||
"value": "#3d8fd1",
|
||||
"type": "color"
|
||||
},
|
||||
"ok": {
|
||||
"value": "#ac9739",
|
||||
"type": "color"
|
||||
},
|
||||
"error": {
|
||||
"value": "#c94922",
|
||||
"type": "color"
|
||||
},
|
||||
"warning": {
|
||||
"value": "#c08b30",
|
||||
"type": "color"
|
||||
},
|
||||
"info": {
|
||||
"value": "#3d8fd1",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"background": {
|
||||
"100": {
|
||||
"base": {
|
||||
"value": "#363f62",
|
||||
"type": "color"
|
||||
},
|
||||
"hovered": {
|
||||
"value": "#444c6f",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#51597b",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"300": {
|
||||
"base": {
|
||||
"value": "#293256",
|
||||
"type": "color"
|
||||
},
|
||||
"hovered": {
|
||||
"value": "#363f62",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#444c6f",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"base": {
|
||||
"value": "#202746",
|
||||
"type": "color"
|
||||
},
|
||||
"hovered": {
|
||||
"value": "#222a4a",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#252d4e",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"on300": {
|
||||
"base": {
|
||||
"value": "#202746",
|
||||
"type": "color"
|
||||
},
|
||||
"hovered": {
|
||||
"value": "#222a4a",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#252d4e",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"on500": {
|
||||
"base": {
|
||||
"value": "#363f62",
|
||||
"type": "color"
|
||||
},
|
||||
"hovered": {
|
||||
"value": "#444c6f",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#51597b",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"ok": {
|
||||
"base": {
|
||||
"value": "#ac973926",
|
||||
"type": "color"
|
||||
},
|
||||
"hovered": {
|
||||
"value": "#ac973933",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#ac973940",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"base": {
|
||||
"value": "#c9492226",
|
||||
"type": "color"
|
||||
},
|
||||
"hovered": {
|
||||
"value": "#c9492233",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#c9492240",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"warning": {
|
||||
"base": {
|
||||
"value": "#c08b3026",
|
||||
"type": "color"
|
||||
},
|
||||
"hovered": {
|
||||
"value": "#c08b3033",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#c08b3040",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"info": {
|
||||
"base": {
|
||||
"value": "#3d8fd126",
|
||||
"type": "color"
|
||||
},
|
||||
"hovered": {
|
||||
"value": "#3d8fd133",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#3d8fd140",
|
||||
"type": "color"
|
||||
}
|
||||
}
|
||||
},
|
||||
"border": {
|
||||
"primary": {
|
||||
"value": "#202746",
|
||||
"type": "color"
|
||||
},
|
||||
"secondary": {
|
||||
"value": "#293256",
|
||||
"type": "color"
|
||||
},
|
||||
"muted": {
|
||||
"value": "#6b7394",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#6b7394",
|
||||
"type": "color"
|
||||
},
|
||||
"onMedia": {
|
||||
"value": "#2027461a",
|
||||
"type": "color"
|
||||
},
|
||||
"ok": {
|
||||
"value": "#ac973926",
|
||||
"type": "color"
|
||||
},
|
||||
"error": {
|
||||
"value": "#c9492226",
|
||||
"type": "color"
|
||||
},
|
||||
"warning": {
|
||||
"value": "#c08b3026",
|
||||
"type": "color"
|
||||
},
|
||||
"info": {
|
||||
"value": "#3d8fd126",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"editor": {
|
||||
"background": {
|
||||
"value": "#202746",
|
||||
"type": "color"
|
||||
},
|
||||
"indent_guide": {
|
||||
"value": "#6b7394",
|
||||
"type": "color"
|
||||
},
|
||||
"indent_guide_active": {
|
||||
"value": "#293256",
|
||||
"type": "color"
|
||||
},
|
||||
"line": {
|
||||
"active": {
|
||||
"value": "#293256",
|
||||
"type": "color"
|
||||
},
|
||||
"highlighted": {
|
||||
"value": "#363f62",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"highlight": {
|
||||
"selection": {
|
||||
"value": "#3d8fd13d",
|
||||
"type": "color"
|
||||
},
|
||||
"occurrence": {
|
||||
"value": "#5e66873d",
|
||||
"type": "color"
|
||||
},
|
||||
"activeOccurrence": {
|
||||
"value": "#5e66877a",
|
||||
"type": "color"
|
||||
},
|
||||
"matchingBracket": {
|
||||
"value": "#252d4e",
|
||||
"type": "color"
|
||||
},
|
||||
"match": {
|
||||
"value": "#1a2a6d",
|
||||
"type": "color"
|
||||
},
|
||||
"activeMatch": {
|
||||
"value": "#3d56c47a",
|
||||
"type": "color"
|
||||
},
|
||||
"related": {
|
||||
"value": "#222a4a",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"gutter": {
|
||||
"primary": {
|
||||
"value": "#898ea4",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#f5f7ff",
|
||||
"type": "color"
|
||||
}
|
||||
}
|
||||
},
|
||||
"syntax": {
|
||||
"primary": {
|
||||
"value": "#f5f7ff",
|
||||
"type": "color"
|
||||
},
|
||||
"comment": {
|
||||
"value": "#979db4",
|
||||
"type": "color"
|
||||
},
|
||||
"keyword": {
|
||||
"value": "#3d8fd1",
|
||||
"type": "color"
|
||||
},
|
||||
"function": {
|
||||
"value": "#c08b30",
|
||||
"type": "color"
|
||||
},
|
||||
"type": {
|
||||
"value": "#22a2c9",
|
||||
"type": "color"
|
||||
},
|
||||
"variant": {
|
||||
"value": "#3d8fd1",
|
||||
"type": "color"
|
||||
},
|
||||
"property": {
|
||||
"value": "#3d8fd1",
|
||||
"type": "color"
|
||||
},
|
||||
"enum": {
|
||||
"value": "#c76b29",
|
||||
"type": "color"
|
||||
},
|
||||
"operator": {
|
||||
"value": "#c76b29",
|
||||
"type": "color"
|
||||
},
|
||||
"string": {
|
||||
"value": "#c76b29",
|
||||
"type": "color"
|
||||
},
|
||||
"number": {
|
||||
"value": "#ac9739",
|
||||
"type": "color"
|
||||
},
|
||||
"boolean": {
|
||||
"value": "#ac9739",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"player": {
|
||||
"1": {
|
||||
"baseColor": {
|
||||
"value": "#3d8fd1",
|
||||
"type": "color"
|
||||
},
|
||||
"cursorColor": {
|
||||
"value": "#3d8fd1",
|
||||
"type": "color"
|
||||
},
|
||||
"selectionColor": {
|
||||
"value": "#3d8fd13d",
|
||||
"type": "color"
|
||||
},
|
||||
"borderColor": {
|
||||
"value": "#3d8fd1cc",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"2": {
|
||||
"baseColor": {
|
||||
"value": "#ac9739",
|
||||
"type": "color"
|
||||
},
|
||||
"cursorColor": {
|
||||
"value": "#ac9739",
|
||||
"type": "color"
|
||||
},
|
||||
"selectionColor": {
|
||||
"value": "#ac97393d",
|
||||
"type": "color"
|
||||
},
|
||||
"borderColor": {
|
||||
"value": "#ac9739cc",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"3": {
|
||||
"baseColor": {
|
||||
"value": "#9c637a",
|
||||
"type": "color"
|
||||
},
|
||||
"cursorColor": {
|
||||
"value": "#9c637a",
|
||||
"type": "color"
|
||||
},
|
||||
"selectionColor": {
|
||||
"value": "#9c637a3d",
|
||||
"type": "color"
|
||||
},
|
||||
"borderColor": {
|
||||
"value": "#9c637acc",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"4": {
|
||||
"baseColor": {
|
||||
"value": "#c76b29",
|
||||
"type": "color"
|
||||
},
|
||||
"cursorColor": {
|
||||
"value": "#c76b29",
|
||||
"type": "color"
|
||||
},
|
||||
"selectionColor": {
|
||||
"value": "#c76b293d",
|
||||
"type": "color"
|
||||
},
|
||||
"borderColor": {
|
||||
"value": "#c76b29cc",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"5": {
|
||||
"baseColor": {
|
||||
"value": "#6679cc",
|
||||
"type": "color"
|
||||
},
|
||||
"cursorColor": {
|
||||
"value": "#6679cc",
|
||||
"type": "color"
|
||||
},
|
||||
"selectionColor": {
|
||||
"value": "#6679cc3d",
|
||||
"type": "color"
|
||||
},
|
||||
"borderColor": {
|
||||
"value": "#6679cccc",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"6": {
|
||||
"baseColor": {
|
||||
"value": "#22a2c9",
|
||||
"type": "color"
|
||||
},
|
||||
"cursorColor": {
|
||||
"value": "#22a2c9",
|
||||
"type": "color"
|
||||
},
|
||||
"selectionColor": {
|
||||
"value": "#22a2c93d",
|
||||
"type": "color"
|
||||
},
|
||||
"borderColor": {
|
||||
"value": "#22a2c9cc",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"7": {
|
||||
"baseColor": {
|
||||
"value": "#c94922",
|
||||
"type": "color"
|
||||
},
|
||||
"cursorColor": {
|
||||
"value": "#c94922",
|
||||
"type": "color"
|
||||
},
|
||||
"selectionColor": {
|
||||
"value": "#c949223d",
|
||||
"type": "color"
|
||||
},
|
||||
"borderColor": {
|
||||
"value": "#c94922cc",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"8": {
|
||||
"baseColor": {
|
||||
"value": "#c08b30",
|
||||
"type": "color"
|
||||
},
|
||||
"cursorColor": {
|
||||
"value": "#c08b30",
|
||||
"type": "color"
|
||||
},
|
||||
"selectionColor": {
|
||||
"value": "#c08b303d",
|
||||
"type": "color"
|
||||
},
|
||||
"borderColor": {
|
||||
"value": "#c08b30cc",
|
||||
"type": "color"
|
||||
}
|
||||
}
|
||||
},
|
||||
"shadowAlpha": {
|
||||
"value": 0.24,
|
||||
"type": "number"
|
||||
}
|
||||
}
|
519
styles/dist/sulphurpool-light.json
vendored
Normal file
519
styles/dist/sulphurpool-light.json
vendored
Normal file
@ -0,0 +1,519 @@
|
||||
{
|
||||
"meta": {
|
||||
"themeName": "sulphurpool-light"
|
||||
},
|
||||
"text": {
|
||||
"primary": {
|
||||
"value": "#293256",
|
||||
"type": "color"
|
||||
},
|
||||
"secondary": {
|
||||
"value": "#5e6687",
|
||||
"type": "color"
|
||||
},
|
||||
"muted": {
|
||||
"value": "#5e6687",
|
||||
"type": "color"
|
||||
},
|
||||
"placeholder": {
|
||||
"value": "#6b7394",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#202746",
|
||||
"type": "color"
|
||||
},
|
||||
"feature": {
|
||||
"value": "#3d8fd1",
|
||||
"type": "color"
|
||||
},
|
||||
"ok": {
|
||||
"value": "#ac9739",
|
||||
"type": "color"
|
||||
},
|
||||
"error": {
|
||||
"value": "#c94922",
|
||||
"type": "color"
|
||||
},
|
||||
"warning": {
|
||||
"value": "#c08b30",
|
||||
"type": "color"
|
||||
},
|
||||
"info": {
|
||||
"value": "#3d8fd1",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"icon": {
|
||||
"primary": {
|
||||
"value": "#293256",
|
||||
"type": "color"
|
||||
},
|
||||
"secondary": {
|
||||
"value": "#5e6687",
|
||||
"type": "color"
|
||||
},
|
||||
"muted": {
|
||||
"value": "#5e6687",
|
||||
"type": "color"
|
||||
},
|
||||
"placeholder": {
|
||||
"value": "#6b7394",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#202746",
|
||||
"type": "color"
|
||||
},
|
||||
"feature": {
|
||||
"value": "#3d8fd1",
|
||||
"type": "color"
|
||||
},
|
||||
"ok": {
|
||||
"value": "#ac9739",
|
||||
"type": "color"
|
||||
},
|
||||
"error": {
|
||||
"value": "#c94922",
|
||||
"type": "color"
|
||||
},
|
||||
"warning": {
|
||||
"value": "#c08b30",
|
||||
"type": "color"
|
||||
},
|
||||
"info": {
|
||||
"value": "#3d8fd1",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"background": {
|
||||
"100": {
|
||||
"base": {
|
||||
"value": "#cdd1e2",
|
||||
"type": "color"
|
||||
},
|
||||
"hovered": {
|
||||
"value": "#bbc0d3",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#a9aec3",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"300": {
|
||||
"base": {
|
||||
"value": "#dfe2f1",
|
||||
"type": "color"
|
||||
},
|
||||
"hovered": {
|
||||
"value": "#cdd1e2",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#bbc0d3",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"base": {
|
||||
"value": "#f5f7ff",
|
||||
"type": "color"
|
||||
},
|
||||
"hovered": {
|
||||
"value": "#f0f2fc",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#eaedf8",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"on300": {
|
||||
"base": {
|
||||
"value": "#f5f7ff",
|
||||
"type": "color"
|
||||
},
|
||||
"hovered": {
|
||||
"value": "#f0f2fc",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#eaedf8",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"on500": {
|
||||
"base": {
|
||||
"value": "#cdd1e2",
|
||||
"type": "color"
|
||||
},
|
||||
"hovered": {
|
||||
"value": "#bbc0d3",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#a9aec3",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"ok": {
|
||||
"base": {
|
||||
"value": "#ac973926",
|
||||
"type": "color"
|
||||
},
|
||||
"hovered": {
|
||||
"value": "#ac973933",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#ac973940",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"base": {
|
||||
"value": "#c9492226",
|
||||
"type": "color"
|
||||
},
|
||||
"hovered": {
|
||||
"value": "#c9492233",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#c9492240",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"warning": {
|
||||
"base": {
|
||||
"value": "#c08b3026",
|
||||
"type": "color"
|
||||
},
|
||||
"hovered": {
|
||||
"value": "#c08b3033",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#c08b3040",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"info": {
|
||||
"base": {
|
||||
"value": "#3d8fd126",
|
||||
"type": "color"
|
||||
},
|
||||
"hovered": {
|
||||
"value": "#3d8fd133",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#3d8fd140",
|
||||
"type": "color"
|
||||
}
|
||||
}
|
||||
},
|
||||
"border": {
|
||||
"primary": {
|
||||
"value": "#bbc0d3",
|
||||
"type": "color"
|
||||
},
|
||||
"secondary": {
|
||||
"value": "#cdd1e2",
|
||||
"type": "color"
|
||||
},
|
||||
"muted": {
|
||||
"value": "#dfe2f1",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#6b7394",
|
||||
"type": "color"
|
||||
},
|
||||
"onMedia": {
|
||||
"value": "#f5f7ff1a",
|
||||
"type": "color"
|
||||
},
|
||||
"ok": {
|
||||
"value": "#ac973926",
|
||||
"type": "color"
|
||||
},
|
||||
"error": {
|
||||
"value": "#c9492226",
|
||||
"type": "color"
|
||||
},
|
||||
"warning": {
|
||||
"value": "#c08b3026",
|
||||
"type": "color"
|
||||
},
|
||||
"info": {
|
||||
"value": "#3d8fd126",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"editor": {
|
||||
"background": {
|
||||
"value": "#f5f7ff",
|
||||
"type": "color"
|
||||
},
|
||||
"indent_guide": {
|
||||
"value": "#dfe2f1",
|
||||
"type": "color"
|
||||
},
|
||||
"indent_guide_active": {
|
||||
"value": "#cdd1e2",
|
||||
"type": "color"
|
||||
},
|
||||
"line": {
|
||||
"active": {
|
||||
"value": "#dfe2f1",
|
||||
"type": "color"
|
||||
},
|
||||
"highlighted": {
|
||||
"value": "#cdd1e2",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"highlight": {
|
||||
"selection": {
|
||||
"value": "#3d8fd13d",
|
||||
"type": "color"
|
||||
},
|
||||
"occurrence": {
|
||||
"value": "#979db41f",
|
||||
"type": "color"
|
||||
},
|
||||
"activeOccurrence": {
|
||||
"value": "#979db43d",
|
||||
"type": "color"
|
||||
},
|
||||
"matchingBracket": {
|
||||
"value": "#eaedf8",
|
||||
"type": "color"
|
||||
},
|
||||
"match": {
|
||||
"value": "#bcc6f7",
|
||||
"type": "color"
|
||||
},
|
||||
"activeMatch": {
|
||||
"value": "#7b8ddc3d",
|
||||
"type": "color"
|
||||
},
|
||||
"related": {
|
||||
"value": "#f0f2fc",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"gutter": {
|
||||
"primary": {
|
||||
"value": "#6b7394",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
"value": "#202746",
|
||||
"type": "color"
|
||||
}
|
||||
}
|
||||
},
|
||||
"syntax": {
|
||||
"primary": {
|
||||
"value": "#202746",
|
||||
"type": "color"
|
||||
},
|
||||
"comment": {
|
||||
"value": "#5e6687",
|
||||
"type": "color"
|
||||
},
|
||||
"keyword": {
|
||||
"value": "#3d8fd1",
|
||||
"type": "color"
|
||||
},
|
||||
"function": {
|
||||
"value": "#c08b30",
|
||||
"type": "color"
|
||||
},
|
||||
"type": {
|
||||
"value": "#22a2c9",
|
||||
"type": "color"
|
||||
},
|
||||
"variant": {
|
||||
"value": "#3d8fd1",
|
||||
"type": "color"
|
||||
},
|
||||
"property": {
|
||||
"value": "#3d8fd1",
|
||||
"type": "color"
|
||||
},
|
||||
"enum": {
|
||||
"value": "#c76b29",
|
||||
"type": "color"
|
||||
},
|
||||
"operator": {
|
||||
"value": "#c76b29",
|
||||
"type": "color"
|
||||
},
|
||||
"string": {
|
||||
"value": "#c76b29",
|
||||
"type": "color"
|
||||
},
|
||||
"number": {
|
||||
"value": "#ac9739",
|
||||
"type": "color"
|
||||
},
|
||||
"boolean": {
|
||||
"value": "#ac9739",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"player": {
|
||||
"1": {
|
||||
"baseColor": {
|
||||
"value": "#3d8fd1",
|
||||
"type": "color"
|
||||
},
|
||||
"cursorColor": {
|
||||
"value": "#3d8fd1",
|
||||
"type": "color"
|
||||
},
|
||||
"selectionColor": {
|
||||
"value": "#3d8fd13d",
|
||||
"type": "color"
|
||||
},
|
||||
"borderColor": {
|
||||
"value": "#3d8fd1cc",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"2": {
|
||||
"baseColor": {
|
||||
"value": "#ac9739",
|
||||
"type": "color"
|
||||
},
|
||||
"cursorColor": {
|
||||
"value": "#ac9739",
|
||||
"type": "color"
|
||||
},
|
||||
"selectionColor": {
|
||||
"value": "#ac97393d",
|
||||
"type": "color"
|
||||
},
|
||||
"borderColor": {
|
||||
"value": "#ac9739cc",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"3": {
|
||||
"baseColor": {
|
||||
"value": "#9c637a",
|
||||
"type": "color"
|
||||
},
|
||||
"cursorColor": {
|
||||
"value": "#9c637a",
|
||||
"type": "color"
|
||||
},
|
||||
"selectionColor": {
|
||||
"value": "#9c637a3d",
|
||||
"type": "color"
|
||||
},
|
||||
"borderColor": {
|
||||
"value": "#9c637acc",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"4": {
|
||||
"baseColor": {
|
||||
"value": "#c76b29",
|
||||
"type": "color"
|
||||
},
|
||||
"cursorColor": {
|
||||
"value": "#c76b29",
|
||||
"type": "color"
|
||||
},
|
||||
"selectionColor": {
|
||||
"value": "#c76b293d",
|
||||
"type": "color"
|
||||
},
|
||||
"borderColor": {
|
||||
"value": "#c76b29cc",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"5": {
|
||||
"baseColor": {
|
||||
"value": "#6679cc",
|
||||
"type": "color"
|
||||
},
|
||||
"cursorColor": {
|
||||
"value": "#6679cc",
|
||||
"type": "color"
|
||||
},
|
||||
"selectionColor": {
|
||||
"value": "#6679cc3d",
|
||||
"type": "color"
|
||||
},
|
||||
"borderColor": {
|
||||
"value": "#6679cccc",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"6": {
|
||||
"baseColor": {
|
||||
"value": "#22a2c9",
|
||||
"type": "color"
|
||||
},
|
||||
"cursorColor": {
|
||||
"value": "#22a2c9",
|
||||
"type": "color"
|
||||
},
|
||||
"selectionColor": {
|
||||
"value": "#22a2c93d",
|
||||
"type": "color"
|
||||
},
|
||||
"borderColor": {
|
||||
"value": "#22a2c9cc",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"7": {
|
||||
"baseColor": {
|
||||
"value": "#c94922",
|
||||
"type": "color"
|
||||
},
|
||||
"cursorColor": {
|
||||
"value": "#c94922",
|
||||
"type": "color"
|
||||
},
|
||||
"selectionColor": {
|
||||
"value": "#c949223d",
|
||||
"type": "color"
|
||||
},
|
||||
"borderColor": {
|
||||
"value": "#c94922cc",
|
||||
"type": "color"
|
||||
}
|
||||
},
|
||||
"8": {
|
||||
"baseColor": {
|
||||
"value": "#c08b30",
|
||||
"type": "color"
|
||||
},
|
||||
"cursorColor": {
|
||||
"value": "#c08b30",
|
||||
"type": "color"
|
||||
},
|
||||
"selectionColor": {
|
||||
"value": "#c08b303d",
|
||||
"type": "color"
|
||||
},
|
||||
"borderColor": {
|
||||
"value": "#c08b30cc",
|
||||
"type": "color"
|
||||
}
|
||||
}
|
||||
},
|
||||
"shadowAlpha": {
|
||||
"value": 0.12,
|
||||
"type": "number"
|
||||
}
|
||||
}
|
30
styles/dist/tokens.json
vendored
30
styles/dist/tokens.json
vendored
@ -1917,15 +1917,15 @@
|
||||
},
|
||||
"border": {
|
||||
"primary": {
|
||||
"value": "#8b8792",
|
||||
"value": "#b7b3bd",
|
||||
"type": "color"
|
||||
},
|
||||
"secondary": {
|
||||
"value": "#8b8792",
|
||||
"value": "#ccc9d2",
|
||||
"type": "color"
|
||||
},
|
||||
"muted": {
|
||||
"value": "#655f6d",
|
||||
"value": "#e2dfe7",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
@ -1959,11 +1959,11 @@
|
||||
"type": "color"
|
||||
},
|
||||
"indent_guide": {
|
||||
"value": "#655f6d",
|
||||
"value": "#e2dfe7",
|
||||
"type": "color"
|
||||
},
|
||||
"indent_guide_active": {
|
||||
"value": "#8b8792",
|
||||
"value": "#ccc9d2",
|
||||
"type": "color"
|
||||
},
|
||||
"line": {
|
||||
@ -2955,15 +2955,15 @@
|
||||
},
|
||||
"border": {
|
||||
"primary": {
|
||||
"value": "#93a1a1",
|
||||
"value": "#c1c5bb",
|
||||
"type": "color"
|
||||
},
|
||||
"secondary": {
|
||||
"value": "#93a1a1",
|
||||
"value": "#d7d6c8",
|
||||
"type": "color"
|
||||
},
|
||||
"muted": {
|
||||
"value": "#657b83",
|
||||
"value": "#eee8d5",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
@ -2997,11 +2997,11 @@
|
||||
"type": "color"
|
||||
},
|
||||
"indent_guide": {
|
||||
"value": "#657b83",
|
||||
"value": "#eee8d5",
|
||||
"type": "color"
|
||||
},
|
||||
"indent_guide_active": {
|
||||
"value": "#93a1a1",
|
||||
"value": "#d7d6c8",
|
||||
"type": "color"
|
||||
},
|
||||
"line": {
|
||||
@ -3993,15 +3993,15 @@
|
||||
},
|
||||
"border": {
|
||||
"primary": {
|
||||
"value": "#979db4",
|
||||
"value": "#bbc0d3",
|
||||
"type": "color"
|
||||
},
|
||||
"secondary": {
|
||||
"value": "#979db4",
|
||||
"value": "#cdd1e2",
|
||||
"type": "color"
|
||||
},
|
||||
"muted": {
|
||||
"value": "#6b7394",
|
||||
"value": "#dfe2f1",
|
||||
"type": "color"
|
||||
},
|
||||
"active": {
|
||||
@ -4035,11 +4035,11 @@
|
||||
"type": "color"
|
||||
},
|
||||
"indent_guide": {
|
||||
"value": "#6b7394",
|
||||
"value": "#dfe2f1",
|
||||
"type": "color"
|
||||
},
|
||||
"indent_guide_active": {
|
||||
"value": "#979db4",
|
||||
"value": "#cdd1e2",
|
||||
"type": "color"
|
||||
},
|
||||
"line": {
|
||||
|
@ -42,6 +42,7 @@ export default function contactsPanel(theme: Theme) {
|
||||
|
||||
return {
|
||||
...panel,
|
||||
padding: { top: panel.padding.top, bottom: 0 },
|
||||
userQueryEditor: {
|
||||
background: backgroundColor(theme, 500),
|
||||
cornerRadius: 6,
|
||||
@ -136,5 +137,16 @@ export default function contactsPanel(theme: Theme) {
|
||||
background: backgroundColor(theme, 300, "active"),
|
||||
}
|
||||
},
|
||||
inviteRow: {
|
||||
padding: {
|
||||
left: sidePadding,
|
||||
right: sidePadding
|
||||
},
|
||||
border: { top: true, width: 1, color: borderColor(theme, "primary") },
|
||||
text: text(theme, "sans", "primary", { size: "sm" }),
|
||||
hover: {
|
||||
text: text(theme, "sans", "primary", { size: "sm", underline: true })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user