Dashboard: a grid of widgets, what fires, what is up, and the stream
DashboardGrid lays widgets on columns: a tile's strip drags it and its corner resizes it a cell at a time, others move down and rise to fill gaps, a focused tile steps with the arrows, and under the theme's stack_below width the tiles stand in one column. DashboardCard frames a widget. AlertList acknowledges a firing alert from its row and opens one on Enter; IncidentCard shows an incident's gravity, phase and updates. UptimeBar, ServiceStatus and HealthCheck show what is up, a check's detail wrapping under its name when narrow. EventStream keeps kinds and holds the list while new events count up. RefreshIntervalSelector and DashboardFilterBar set the window, the filters and the refresh. StatCard, MetricsChart, ResourceGauge, LogStream, TraceWaterfall, SystemMonitor, ProcessTable and NetworkGraph point to their homes; the maps wait for T41.
This commit is contained in:
1 parent
f3332f935b
commit
9fddf4b6bf
17 files changed
+2661
-18
No files matched your search
@@ -0,0 +1,176 @@
|
||||
use gpui::SharedString;
|
||||
|
||||
/// A tile's place on a dashboard grid: its key, and its column, row, width and height in cells.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Tile {
|
||||
pub key: SharedString,
|
||||
pub x: u32,
|
||||
pub y: u32,
|
||||
pub w: u32,
|
||||
pub h: u32,
|
||||
}
|
||||
|
||||
impl Tile {
|
||||
pub fn new(key: impl Into<SharedString>, (x, y): (u32, u32), (w, h): (u32, u32)) -> Self {
|
||||
assert!(w > 0 && h > 0, "a tile of {w} by {h} cells");
|
||||
Self {
|
||||
key: key.into(),
|
||||
x,
|
||||
y,
|
||||
w,
|
||||
h,
|
||||
}
|
||||
}
|
||||
|
||||
fn overlaps(&self, other: &Tile) -> bool {
|
||||
self.x < other.x + other.w
|
||||
&& other.x < self.x + self.w
|
||||
&& self.y < other.y + other.h
|
||||
&& other.y < self.y + self.h
|
||||
}
|
||||
|
||||
/// This tile kept inside `columns`: at least a cell each way, no wider than the grid, and not past its right edge.
|
||||
fn fitted(mut self, columns: u32) -> Tile {
|
||||
self.w = self.w.clamp(1, columns);
|
||||
self.h = self.h.max(1);
|
||||
self.x = self.x.min(columns - self.w);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Tiles risen as far as the ones above allow, top first.
|
||||
fn compacted(mut tiles: Vec<Tile>) -> Vec<Tile> {
|
||||
tiles.sort_by_key(|tile| (tile.y, tile.x));
|
||||
let mut out: Vec<Tile> = Vec::new();
|
||||
for mut tile in tiles {
|
||||
while tile.y > 0 {
|
||||
let higher = Tile {
|
||||
y: tile.y - 1,
|
||||
..tile.clone()
|
||||
};
|
||||
if out.iter().any(|placed| placed.overlaps(&higher)) {
|
||||
break;
|
||||
}
|
||||
tile = higher;
|
||||
}
|
||||
out.push(tile);
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// The tiles once `to` takes the place of the tile of its key: it goes where it was put, the tiles it lands on move down below it, then every tile, it too, rises as far as the ones above allow.
|
||||
pub fn arranged(tiles: &[Tile], to: Tile, columns: u32) -> Vec<Tile> {
|
||||
assert!(columns > 0, "a grid of no columns");
|
||||
assert!(
|
||||
tiles.iter().any(|tile| tile.key == to.key),
|
||||
"no tile {} on the grid",
|
||||
to.key
|
||||
);
|
||||
let mut placed = vec![to.clone().fitted(columns)];
|
||||
let mut rest: Vec<Tile> = tiles
|
||||
.iter()
|
||||
.filter(|tile| tile.key != to.key)
|
||||
.cloned()
|
||||
.collect();
|
||||
rest.sort_by_key(|tile| (tile.y, tile.x));
|
||||
for mut tile in rest {
|
||||
while let Some(hit) = placed.iter().find(|placed| placed.overlaps(&tile)) {
|
||||
tile.y = hit.y + hit.h;
|
||||
}
|
||||
placed.push(tile);
|
||||
}
|
||||
compacted(placed)
|
||||
}
|
||||
|
||||
/// Where a tile goes on a step down or up: below the next tile under it, or into the place of the next one above; none at the edge. Both overlap its columns.
|
||||
pub fn stepped(tiles: &[Tile], key: &str, down: bool) -> Option<Tile> {
|
||||
let tile = tiles
|
||||
.iter()
|
||||
.find(|tile| tile.key == key)
|
||||
.unwrap_or_else(|| panic!("no tile {key} on the grid"));
|
||||
let beside = |other: &&Tile| {
|
||||
other.key != tile.key && other.x < tile.x + tile.w && tile.x < other.x + other.w
|
||||
};
|
||||
if down {
|
||||
tiles
|
||||
.iter()
|
||||
.filter(beside)
|
||||
.filter(|other| other.y >= tile.y + tile.h)
|
||||
.min_by_key(|other| other.y)
|
||||
.map(|next| Tile {
|
||||
y: next.y + next.h,
|
||||
..tile.clone()
|
||||
})
|
||||
} else {
|
||||
tiles
|
||||
.iter()
|
||||
.filter(beside)
|
||||
.filter(|other| other.y + other.h <= tile.y)
|
||||
.max_by_key(|other| other.y)
|
||||
.map(|above| Tile {
|
||||
y: above.y,
|
||||
..tile.clone()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Tile, arranged, stepped};
|
||||
|
||||
fn at(tiles: &[Tile], key: &str) -> (u32, u32, u32, u32) {
|
||||
let tile = tiles.iter().find(|tile| tile.key == key).expect("a tile");
|
||||
(tile.x, tile.y, tile.w, tile.h)
|
||||
}
|
||||
|
||||
fn grid() -> Vec<Tile> {
|
||||
vec![
|
||||
Tile::new("a", (0, 0), (6, 2)),
|
||||
Tile::new("b", (6, 0), (6, 2)),
|
||||
Tile::new("c", (0, 2), (12, 3)),
|
||||
]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_tile_dropped_on_another_pushes_it_below() {
|
||||
let moved = arranged(&grid(), Tile::new("b", (0, 0), (6, 2)), 12);
|
||||
assert_eq!(at(&moved, "b"), (0, 0, 6, 2), "the dropped tile stays");
|
||||
assert_eq!(at(&moved, "a"), (0, 2, 6, 2), "a goes below b");
|
||||
assert_eq!(at(&moved, "c"), (0, 4, 12, 3), "and c below a");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tiles_rise_into_the_gap_a_tile_leaves() {
|
||||
let moved = arranged(&grid(), Tile::new("a", (0, 9), (6, 2)), 12);
|
||||
assert_eq!(at(&moved, "c"), (0, 2, 12, 3), "c keeps below b");
|
||||
assert_eq!(at(&moved, "a"), (0, 5, 6, 2), "a rises to rest on c");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_tile_stays_inside_the_grid() {
|
||||
let moved = arranged(&grid(), Tile::new("a", (10, 0), (6, 2)), 12);
|
||||
assert_eq!(
|
||||
at(&moved, "a"),
|
||||
(6, 0, 6, 2),
|
||||
"pulled back from the right edge"
|
||||
);
|
||||
let wide = arranged(&grid(), Tile::new("b", (0, 0), (20, 1)), 12);
|
||||
assert_eq!(at(&wide, "b"), (0, 0, 12, 1), "no wider than the grid");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_step_down_passes_the_tile_under_and_a_step_up_takes_its_place() {
|
||||
let down = stepped(&grid(), "a", true).expect("c lies under a");
|
||||
assert_eq!((down.x, down.y), (0, 5), "a steps under the bottom of c");
|
||||
let moved = arranged(&grid(), down, 12);
|
||||
assert_eq!(at(&moved, "c"), (0, 2, 12, 3), "c rests under b");
|
||||
assert_eq!(at(&moved, "a"), (0, 5, 6, 2), "a rests under c");
|
||||
let up = stepped(&moved, "a", false).expect("c lies over a");
|
||||
assert_eq!(
|
||||
at(&arranged(&moved, up, 12), "a"),
|
||||
(0, 0, 6, 2),
|
||||
"back on top"
|
||||
);
|
||||
assert_eq!(stepped(&grid(), "b", false), None, "nothing over b");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user