Files
Ely-GPUI-Components/examples/gallery/pages/devtools/db.rs
T

283 lines
11 KiB
Rust

use ely_gpui_component::{
canvas::Viewport,
devtools::{
Connection, ConnectionForm, ConnectionManager, ConnectionState, DbTable, ERDiagram, Engine,
Field, Index, IndexManager, Schema, SchemaTree, TableStructureEditor, TestState,
},
theme::{ActiveTheme, Radius},
};
use gpui::{App, IntoElement, ParentElement, SharedString, Styled, Window, div, px};
use crate::ui::{change, keep, section};
pub(super) fn tables() -> Vec<DbTable> {
vec![
DbTable::new(
"users",
[
Field::new("id", "int").primary(),
Field::new("email", "text"),
Field::new("name", "text").nullable(),
Field::new("created_at", "timestamptz").default_value("now()"),
],
),
DbTable::new(
"orders",
[
Field::new("id", "int").primary(),
Field::new("user_id", "int").refers("users", "id"),
Field::new("total", "numeric"),
Field::new("placed_at", "timestamptz"),
],
),
DbTable::new(
"order_items",
[
Field::new("order_id", "int").refers("orders", "id"),
Field::new("sku", "text"),
Field::new("quantity", "int").default_value("1"),
],
),
DbTable::new(
"recent_orders",
[Field::new("id", "int"), Field::new("total", "numeric")],
)
.view(),
]
}
/// The connections demo's list, the one picked, the one in the form, and its last test.
struct Desk {
connections: Vec<Connection>,
selected: Option<SharedString>,
editing: Connection,
test: Option<TestState>,
}
impl Desk {
fn new() -> Self {
let mut shop = Connection::new("shop", "Shop", Engine::Postgres);
shop.database = "shop".into();
shop.user = "app".into();
shop.state = ConnectionState::Connected;
let mut cache = Connection::new("cache", "Cache", Engine::Redis);
cache.host = "10.0.0.12".into();
let mut analytics = Connection::new("analytics", "Analytics", Engine::MySql);
analytics.host = "db.internal".into();
analytics.database = "events".into();
analytics.state = ConnectionState::Failed("Password rejected for user report".into());
let mut notes = Connection::new("notes", "Notes", Engine::Sqlite);
notes.database = "~/notes.db".into();
Self {
editing: shop.clone(),
connections: vec![shop, cache, analytics, notes],
selected: Some("shop".into()),
test: None,
}
}
}
pub fn connections(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
let desk = keep("devtools-connections", Desk::new, window, cx);
let now = desk.read(cx);
let (connections, selected, editing, test) = (
now.connections.clone(),
now.selected.clone(),
now.editing.clone(),
now.test.clone(),
);
let [picked, opened, edited, removed, made, tested, saved] = [(); 7].map(|_| desk.clone());
let mut form = ConnectionForm::new("devtools-connection-form", editing)
.on_test(move |connection, _, cx| {
change(&tested, cx, |desk| {
desk.test = Some(match connection.engine {
Engine::MySql => TestState::Failed("Password rejected".into()),
_ => TestState::Passed(23),
})
})
})
.on_save(move |connection, _, cx| {
change(&saved, cx, |desk| {
match desk
.connections
.iter_mut()
.find(|each| each.key == connection.key)
{
Some(each) => *each = connection.clone(),
None => desk.connections.push(connection.clone()),
}
desk.selected = Some(connection.key.clone());
desk.test = None;
})
});
if let Some(test) = test {
form = form.test(test);
}
section(
"ConnectionManager · ConnectionForm",
"Saved connections with their engine and address, or why one failed. Enter or a double press connects; the pencil opens one in the form, where Test tries it and Save keeps it.",
cx,
)
.child(
div()
.flex()
.flex_wrap()
.gap_6()
.child(
div().w(px(340.)).child(
ConnectionManager::new("devtools-connections", connections)
.selected(selected)
.on_select(move |key, _, cx| change(&picked, cx, |desk| desk.selected = Some(key.clone())))
.on_open(move |key, _, cx| {
change(&opened, cx, |desk| {
let each = desk.connections.iter_mut().find(|each| each.key == *key).expect("a listed connection");
each.state = ConnectionState::Connected;
})
})
.on_edit(move |key, _, cx| {
change(&edited, cx, |desk| {
desk.editing = desk.connections.iter().find(|each| each.key == *key).expect("a listed connection").clone();
desk.test = None;
})
})
.on_remove(move |key, _, cx| change(&removed, cx, |desk| desk.connections.retain(|each| each.key != *key)))
.on_new(move |_, cx| {
change(&made, cx, |desk| {
let key = SharedString::from(format!("new-{}", desk.connections.len() + 1));
desk.editing = Connection::new(key, "", Engine::Postgres);
desk.test = None;
})
}),
),
)
.child(div().w(px(360.)).child(form)),
)
}
/// The schema demo's tree selection and where each table sits in the diagram.
struct Map {
selected: Vec<SharedString>,
places: Vec<(SharedString, (f32, f32))>,
view: Viewport,
}
pub fn schema(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
let map = keep(
"devtools-schema",
|| Map {
selected: vec!["public.orders".into()],
places: vec![
("order_items".into(), (20.0, 40.0)),
("orders".into(), (300.0, 20.0)),
("users".into(), (580.0, 60.0)),
("recent_orders".into(), (20.0, 230.0)),
],
view: Viewport::new(0.0, 0.0, 0.7),
},
window,
cx,
);
let now = map.read(cx);
let (selected, places, view) = (now.selected.clone(), now.places.clone(), now.view);
let [picked, moved, viewed] = [(); 3].map(|_| map.clone());
let theme = cx.theme();
let boxed = |width: f32, height: f32| {
div()
.w(px(width))
.h(px(height))
.rounded(theme.radius(Radius::Lg))
.border_1()
.border_color(theme.colors.border)
.overflow_hidden()
};
section(
"SchemaTree · ERDiagram",
"A database's schemas, tables and views, each field with its type and keys marked. The diagram draws each table as a box of fields and a line from every foreign key to the field it points at; a table's title drags it.",
cx,
)
.child(
div()
.flex()
.flex_wrap()
.gap_6()
.child(
boxed(260.0, 320.0).p_1().child(
SchemaTree::new(
"devtools-schema-tree",
[Schema { name: "public".into(), tables: tables() }],
)
.open(["public", "public.orders"])
.selected(selected)
.on_select(move |keys, _, cx| change(&picked, cx, |map| map.selected = keys.to_vec())),
),
)
.child(
boxed(560.0, 320.0).child(
ERDiagram::new("devtools-erd", view, tables(), places)
.on_move(move |name, (dx, dy), _, cx| {
change(&moved, cx, |map| {
let place = map.places.iter_mut().find(|(each, _)| each == name).expect("a placed table");
place.1 = (place.1.0 + dx, place.1.1 + dy);
})
})
.on_viewport(move |next, _, cx| change(&viewed, cx, |map| map.view = next)),
),
),
)
}
/// The structure demo's fields and indexes.
struct Build {
fields: Vec<Field>,
indexes: Vec<Index>,
}
pub fn structure(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
let build = keep(
"devtools-structure",
|| Build {
fields: tables()[1].fields.clone(),
indexes: vec![
Index {
name: "orders_pkey".into(),
fields: vec!["id".into()],
unique: true,
method: "btree".into(),
},
Index {
name: "orders_user_placed".into(),
fields: vec!["user_id".into(), "placed_at".into()],
unique: false,
method: "btree".into(),
},
],
},
window,
cx,
);
let now = build.read(cx);
let (fields, indexes) = (now.fields.clone(), now.indexes.clone());
let names: Vec<SharedString> = fields.iter().map(|field| field.name.clone()).collect();
let [edited, dropped, created] = [(); 3].map(|_| build.clone());
section(
"TableStructureEditor · IndexManager",
"A table's fields edited in place: a name or a default rewritten on a press, a type, whether it may be empty, and the key. Its indexes list their fields and method, and a new one is drawn up from the table's fields.",
cx,
)
.child(
div()
.flex()
.flex_wrap()
.gap_6()
.child(
div().w(px(660.)).child(TableStructureEditor::new("devtools-structure", fields, move |next, _, cx| change(&edited, cx, |build| build.fields = next)),
),
)
.child(
div().w(px(300.)).child(
IndexManager::new("devtools-indexes", indexes, names, move |index, _, cx| change(&created, cx, |build| build.indexes.push(index)), move |name, _, cx| change(&dropped, cx, |build| build.indexes.retain(|index| index.name != *name))),
),
),
)
}