|
@@ -1,29 +1,21 @@
|
|
|
//! Application data.
|
|
//! Application data.
|
|
|
|
|
|
|
|
-use iced::{
|
|
|
|
|
- Element, Length, Padding,
|
|
|
|
|
- widget::{horizontal_space, row, scrollable, text},
|
|
|
|
|
-};
|
|
|
|
|
-use nom::{
|
|
|
|
|
- Parser,
|
|
|
|
|
- bytes::complete::{tag, take_until, take_until1, take_while, take_while1},
|
|
|
|
|
- character::complete::char,
|
|
|
|
|
- multi::many0,
|
|
|
|
|
- sequence::{preceded, separated_pair},
|
|
|
|
|
-};
|
|
|
|
|
-use std::{
|
|
|
|
|
- cell::RefCell,
|
|
|
|
|
- collections::{HashMap, VecDeque},
|
|
|
|
|
- rc::Rc,
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
use crate::{
|
|
use crate::{
|
|
|
Message,
|
|
Message,
|
|
|
db::{Workspace, WorkspaceEntry},
|
|
db::{Workspace, WorkspaceEntry},
|
|
|
menu::{WorkspaceActionMenu, WorkspaceEntryActionMenu},
|
|
menu::{WorkspaceActionMenu, WorkspaceEntryActionMenu},
|
|
|
- model::{WorkspaceCollection, WorkspaceEntryItem, WorkspaceRequest},
|
|
|
|
|
|
|
+ model::{WorkspaceCollection, WorkspaceEntryItem},
|
|
|
|
|
+ request::WorkspaceRequest,
|
|
|
|
|
+};
|
|
|
|
|
+use iced::widget::{button, rule, scrollable, space};
|
|
|
|
|
+use iced::{
|
|
|
|
|
+ Border, Color, Element, Length,
|
|
|
|
|
+ widget::{container, row, text},
|
|
|
};
|
|
};
|
|
|
|
|
+use iced::{Shadow, widget};
|
|
|
|
|
+use std::{collections::HashMap, f32::consts::PI};
|
|
|
|
|
|
|
|
|
|
+#[derive(Debug)]
|
|
|
pub struct TemplateWorkspace {
|
|
pub struct TemplateWorkspace {
|
|
|
/// Workspace id.
|
|
/// Workspace id.
|
|
|
pub id: i64,
|
|
pub id: i64,
|
|
@@ -36,16 +28,13 @@ pub struct TemplateWorkspace {
|
|
|
pub environments: HashMap<i64, WorkspaceEnvironment>,
|
|
pub environments: HashMap<i64, WorkspaceEnvironment>,
|
|
|
|
|
|
|
|
/// Workspace entities, either directories or requests.
|
|
/// Workspace entities, either directories or requests.
|
|
|
- entries: Vec<RCell<WorkspaceNode>>,
|
|
|
|
|
|
|
+ roots: Vec<i64>,
|
|
|
|
|
|
|
|
/// Current working environment.
|
|
/// Current working environment.
|
|
|
pub env_current: Option<i64>,
|
|
pub env_current: Option<i64>,
|
|
|
|
|
|
|
|
- /// Current open request.
|
|
|
|
|
- req_current: Option<RequestNode>,
|
|
|
|
|
-
|
|
|
|
|
/// Indexes entry IDs directly to their nodes.
|
|
/// Indexes entry IDs directly to their nodes.
|
|
|
- indexes: HashMap<i64, Rc<RefCell<WorkspaceNode>>>,
|
|
|
|
|
|
|
+ indexes: HashMap<i64, WorkspaceNode>,
|
|
|
|
|
|
|
|
/// Workspace menus for adding entries.
|
|
/// Workspace menus for adding entries.
|
|
|
pub menus: HashMap<i64, WorkspaceEntryActionMenu>,
|
|
pub menus: HashMap<i64, WorkspaceEntryActionMenu>,
|
|
@@ -53,59 +42,14 @@ pub struct TemplateWorkspace {
|
|
|
pub menu: WorkspaceActionMenu,
|
|
pub menu: WorkspaceActionMenu,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-impl std::fmt::Debug for TemplateWorkspace {
|
|
|
|
|
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
- // Extract entry IDs to avoid recursive printing
|
|
|
|
|
- let mut entries = HashMap::<i64, Vec<i64>>::new();
|
|
|
|
|
-
|
|
|
|
|
- let mut queue = VecDeque::new();
|
|
|
|
|
-
|
|
|
|
|
- for entry in self.entries.iter() {
|
|
|
|
|
- queue.push_back(entry.clone());
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- while let Some(node) = queue.pop_front() {
|
|
|
|
|
- let node = &*node.borrow();
|
|
|
|
|
-
|
|
|
|
|
- entries
|
|
|
|
|
- .entry(node.id())
|
|
|
|
|
- .and_modify(|children| {
|
|
|
|
|
- children.extend(node.children().iter().map(|c| c.borrow().id()))
|
|
|
|
|
- })
|
|
|
|
|
- .or_insert(node.children().iter().map(|c| c.borrow().id()).collect());
|
|
|
|
|
-
|
|
|
|
|
- queue.extend(node.children().iter().cloned());
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- let indexes = self
|
|
|
|
|
- .indexes
|
|
|
|
|
- .iter()
|
|
|
|
|
- .map(|(k, v)| (k, v.borrow().entry().clone()))
|
|
|
|
|
- .collect::<HashMap<_, _>>();
|
|
|
|
|
-
|
|
|
|
|
- f.debug_struct("TemplateWorkspace")
|
|
|
|
|
- .field("id", &self.id)
|
|
|
|
|
- .field("name", &self.name)
|
|
|
|
|
- .field("environments", &self.environments)
|
|
|
|
|
- .field("entries", &entries)
|
|
|
|
|
- .field("env_current", &self.env_current)
|
|
|
|
|
- .field("req_current", &self.req_current)
|
|
|
|
|
- .field("indexes", &indexes)
|
|
|
|
|
- .field("menus", &self.menus)
|
|
|
|
|
- .field("menu", &self.menu)
|
|
|
|
|
- .finish()
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
impl From<Workspace> for TemplateWorkspace {
|
|
impl From<Workspace> for TemplateWorkspace {
|
|
|
fn from(value: Workspace) -> Self {
|
|
fn from(value: Workspace) -> Self {
|
|
|
Self {
|
|
Self {
|
|
|
id: value.id,
|
|
id: value.id,
|
|
|
name: value.name.clone(),
|
|
name: value.name.clone(),
|
|
|
environments: HashMap::new(),
|
|
environments: HashMap::new(),
|
|
|
- entries: Vec::new(),
|
|
|
|
|
|
|
+ roots: Vec::new(),
|
|
|
env_current: None,
|
|
env_current: None,
|
|
|
- req_current: None,
|
|
|
|
|
indexes: HashMap::new(),
|
|
indexes: HashMap::new(),
|
|
|
menus: HashMap::new(),
|
|
menus: HashMap::new(),
|
|
|
menu: WorkspaceActionMenu::new(value.id, value.name),
|
|
menu: WorkspaceActionMenu::new(value.id, value.name),
|
|
@@ -114,75 +58,93 @@ impl From<Workspace> for TemplateWorkspace {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
impl TemplateWorkspace {
|
|
impl TemplateWorkspace {
|
|
|
- pub fn view(&self) -> Element<'static, Message> {
|
|
|
|
|
- match self.req_current {
|
|
|
|
|
- Some(ref _req) => {
|
|
|
|
|
- // TODO: Display request
|
|
|
|
|
- // iced::widget::container("TODO: Request editor");
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ pub fn view<'a>(&'a self) -> Element<'a, Message> {
|
|
|
|
|
+ let mut sidebar = vec![];
|
|
|
|
|
|
|
|
- None => {
|
|
|
|
|
- // TODO: Display workspace stuff
|
|
|
|
|
- // iced::widget::container(sidebar)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ self.sidebar_recursive(&mut sidebar, &self.roots, 0.);
|
|
|
|
|
+
|
|
|
|
|
+ let workspaces = scrollable(
|
|
|
|
|
+ container(widget::column![self.menu.view(), rule::horizontal(8)].extend(sidebar))
|
|
|
|
|
+ .padding(10), //.width(Length::FillPortion(1)),
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ container(workspaces)
|
|
|
|
|
+ .style(|_| container::Style {
|
|
|
|
|
+ text_color: Some(Color::from_rgba(1., 0.5, 0.5, 0.7)),
|
|
|
|
|
+ background: None,
|
|
|
|
|
+ border: Border {
|
|
|
|
|
+ color: Color::from_rgb(1., 0.5, 0.5),
|
|
|
|
|
+ width: 1.,
|
|
|
|
|
+ radius: (PI / 2.).into(),
|
|
|
|
|
+ },
|
|
|
|
|
+ shadow: Shadow::default(),
|
|
|
|
|
+ snap: true,
|
|
|
|
|
+ })
|
|
|
|
|
+ .height(Length::Fill)
|
|
|
|
|
+ .width(Length::FillPortion(1))
|
|
|
|
|
+ .padding(10)
|
|
|
|
|
+ .into()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ pub fn get_request(&self, id: i64) -> &WorkspaceRequest {
|
|
|
|
|
+ let Some(WorkspaceNode::Request(req)) = self.indexes.get(&id) else {
|
|
|
|
|
+ panic!["no request for id {}", id];
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- let mut sidebar = vec![];
|
|
|
|
|
- let mut visited = Vec::new();
|
|
|
|
|
|
|
+ &req.request
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- self.sidebar_recursive(&mut sidebar, &self.entries, &mut visited, 0);
|
|
|
|
|
|
|
+ pub fn get_request_mut(&mut self, id: i64) -> &mut WorkspaceRequest {
|
|
|
|
|
+ let Some(WorkspaceNode::Request(req)) = self.indexes.get_mut(&id) else {
|
|
|
|
|
+ panic!["no request for id {}", id];
|
|
|
|
|
+ };
|
|
|
|
|
|
|
|
- scrollable(
|
|
|
|
|
- iced::widget::column![self.menu.view(), horizontal_space()]
|
|
|
|
|
- .extend(sidebar)
|
|
|
|
|
- .width(600),
|
|
|
|
|
- )
|
|
|
|
|
- .height(Length::Fill)
|
|
|
|
|
- .into()
|
|
|
|
|
|
|
+ &mut req.request
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- fn sidebar_recursive(
|
|
|
|
|
- &self,
|
|
|
|
|
- sidebar: &mut Vec<Element<'static, Message>>,
|
|
|
|
|
- entries: &[RCell<WorkspaceNode>],
|
|
|
|
|
- visited: &mut Vec<i64>,
|
|
|
|
|
- indent: u16,
|
|
|
|
|
|
|
+ fn sidebar_recursive<'a>(
|
|
|
|
|
+ &'a self,
|
|
|
|
|
+ sidebar: &mut Vec<Element<'a, Message>>,
|
|
|
|
|
+ entries: &[i64],
|
|
|
|
|
+ indent: f32,
|
|
|
) {
|
|
) {
|
|
|
- for entry in entries {
|
|
|
|
|
- let entry = &*entry.borrow();
|
|
|
|
|
- visited.push(entry.entry().id);
|
|
|
|
|
|
|
+ for id in entries {
|
|
|
|
|
+ let Some(entry) = self.indexes.get(&id) else {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
match entry {
|
|
match entry {
|
|
|
WorkspaceNode::Collection(col) => {
|
|
WorkspaceNode::Collection(col) => {
|
|
|
let id = col.entry.entry.id;
|
|
let id = col.entry.entry.id;
|
|
|
- let Some(menu) = self.menus.get(&id) else {
|
|
|
|
|
- tracing::warn!("Missing menu for collection {id}");
|
|
|
|
|
- continue;
|
|
|
|
|
- };
|
|
|
|
|
|
|
|
|
|
- let row = row![menu.view()].padding(Padding::ZERO.left(indent));
|
|
|
|
|
- sidebar.push(row.into());
|
|
|
|
|
- self.sidebar_recursive(sidebar, &col.entries, visited, indent + 10);
|
|
|
|
|
|
|
+ sidebar.push(self.menus[&id].view(indent).into());
|
|
|
|
|
+
|
|
|
|
|
+ if self.menus[&id].expanded {
|
|
|
|
|
+ self.sidebar_recursive(sidebar, &col.entries, indent + 10.);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
WorkspaceNode::Request(req) => {
|
|
WorkspaceNode::Request(req) => {
|
|
|
- sidebar.push(
|
|
|
|
|
- row![text(if req.request.entry.name.is_empty() {
|
|
|
|
|
|
|
+ let row = row![
|
|
|
|
|
+ space::horizontal().width(indent),
|
|
|
|
|
+ button(text(if req.request.entry.name.is_empty() {
|
|
|
"New request".to_string()
|
|
"New request".to_string()
|
|
|
} else {
|
|
} else {
|
|
|
req.request.entry.name.clone()
|
|
req.request.entry.name.clone()
|
|
|
- })]
|
|
|
|
|
- .padding(Padding::ZERO.left(indent))
|
|
|
|
|
- .into(),
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ }))
|
|
|
|
|
+ .on_press(Message::RequestSelected(req.request.entry.id))
|
|
|
|
|
+ .width(Length::Fill)
|
|
|
|
|
+ ];
|
|
|
|
|
+ sidebar.push(row.into());
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
pub fn insert_entry(&mut self, entry: WorkspaceEntryItem) {
|
|
pub fn insert_entry(&mut self, entry: WorkspaceEntryItem) {
|
|
|
|
|
+ let id = entry.id();
|
|
|
let parent = entry.parent_id();
|
|
let parent = entry.parent_id();
|
|
|
|
|
|
|
|
- let id = entry.id();
|
|
|
|
|
- let entry = Rc::new(RefCell::new(match entry {
|
|
|
|
|
|
|
+ let entry = match entry {
|
|
|
WorkspaceEntryItem::Collection(col) => {
|
|
WorkspaceEntryItem::Collection(col) => {
|
|
|
let entry = &col.entry;
|
|
let entry = &col.entry;
|
|
|
self.menus.insert(
|
|
self.menus.insert(
|
|
@@ -191,20 +153,20 @@ impl TemplateWorkspace {
|
|
|
);
|
|
);
|
|
|
WorkspaceNode::Collection(CollectionNode {
|
|
WorkspaceNode::Collection(CollectionNode {
|
|
|
entry: col,
|
|
entry: col,
|
|
|
- parent: parent.as_ref().and_then(|p| self.indexes.get(p).cloned()),
|
|
|
|
|
|
|
+ parent,
|
|
|
entries: vec![],
|
|
entries: vec![],
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
WorkspaceEntryItem::Request(req) => WorkspaceNode::Request(RequestNode {
|
|
WorkspaceEntryItem::Request(req) => WorkspaceNode::Request(RequestNode {
|
|
|
- parent: parent.as_ref().and_then(|p| self.indexes.get(p).cloned()),
|
|
|
|
|
|
|
+ parent,
|
|
|
request: req,
|
|
request: req,
|
|
|
}),
|
|
}),
|
|
|
- }));
|
|
|
|
|
|
|
+ };
|
|
|
|
|
|
|
|
- self.indexes.insert(id, entry.clone());
|
|
|
|
|
|
|
+ self.indexes.insert(id, entry);
|
|
|
|
|
|
|
|
let Some(parent) = parent else {
|
|
let Some(parent) = parent else {
|
|
|
- self.entries.push(entry);
|
|
|
|
|
|
|
+ self.roots.push(id);
|
|
|
return;
|
|
return;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
@@ -212,49 +174,47 @@ impl TemplateWorkspace {
|
|
|
return;
|
|
return;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- match &mut *parent.borrow_mut() {
|
|
|
|
|
- WorkspaceNode::Collection(col) => col.entries.push(entry),
|
|
|
|
|
|
|
+ match parent {
|
|
|
|
|
+ WorkspaceNode::Collection(col) => col.entries.push(id),
|
|
|
WorkspaceNode::Request(_) => {}
|
|
WorkspaceNode::Request(_) => {}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- pub fn update_entries(&mut self, entries: Vec<WorkspaceEntryItem>) {
|
|
|
|
|
|
|
+ pub fn init_entries(&mut self, entries: Vec<WorkspaceEntryItem>) {
|
|
|
let entries = entries
|
|
let entries = entries
|
|
|
.into_iter()
|
|
.into_iter()
|
|
|
- .map(|entry| {
|
|
|
|
|
- Rc::new(RefCell::new(match entry {
|
|
|
|
|
- WorkspaceEntryItem::Collection(col) => {
|
|
|
|
|
- WorkspaceNode::Collection(CollectionNode {
|
|
|
|
|
- entry: col,
|
|
|
|
|
- parent: None,
|
|
|
|
|
- entries: vec![],
|
|
|
|
|
- })
|
|
|
|
|
- }
|
|
|
|
|
- WorkspaceEntryItem::Request(req) => WorkspaceNode::Request(RequestNode {
|
|
|
|
|
- parent: None,
|
|
|
|
|
- request: req,
|
|
|
|
|
- }),
|
|
|
|
|
- }))
|
|
|
|
|
|
|
+ .map(|entry| match entry {
|
|
|
|
|
+ WorkspaceEntryItem::Collection(col) => WorkspaceNode::Collection(CollectionNode {
|
|
|
|
|
+ entry: col,
|
|
|
|
|
+ parent: None,
|
|
|
|
|
+ entries: vec![],
|
|
|
|
|
+ }),
|
|
|
|
|
+ WorkspaceEntryItem::Request(req) => WorkspaceNode::Request(RequestNode {
|
|
|
|
|
+ parent: None,
|
|
|
|
|
+ request: req,
|
|
|
|
|
+ }),
|
|
|
})
|
|
})
|
|
|
.collect::<Vec<_>>();
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
- let mut roots = vec![];
|
|
|
|
|
- let mut children = vec![];
|
|
|
|
|
|
|
+ let mut relations = vec![];
|
|
|
|
|
|
|
|
// Index all entries
|
|
// Index all entries
|
|
|
- for entry in entries.iter() {
|
|
|
|
|
- self.indexes.insert(entry.borrow().id(), entry.clone());
|
|
|
|
|
-
|
|
|
|
|
- if entry.borrow().entry().parent_id.is_some() {
|
|
|
|
|
- children.push(entry.clone())
|
|
|
|
|
|
|
+ for entry in entries {
|
|
|
|
|
+ if let Some(parent_id) = entry.entry().parent_id {
|
|
|
|
|
+ relations.push((entry.entry().id, parent_id))
|
|
|
} else {
|
|
} else {
|
|
|
- roots.push(entry.clone())
|
|
|
|
|
|
|
+ self.roots.push(entry.entry().id)
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ self.indexes.insert(entry.id(), entry);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- for child in children.iter() {
|
|
|
|
|
- let parent = child.borrow().entry().parent_id.unwrap();
|
|
|
|
|
- match &mut *self.indexes[&parent].borrow_mut() {
|
|
|
|
|
|
|
+ for (child, parent) in relations.iter() {
|
|
|
|
|
+ let Some(parent) = self.indexes.get_mut(&parent) else {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ match parent {
|
|
|
WorkspaceNode::Collection(col) => col.entries.push(child.clone()),
|
|
WorkspaceNode::Collection(col) => col.entries.push(child.clone()),
|
|
|
WorkspaceNode::Request(_) => {}
|
|
WorkspaceNode::Request(_) => {}
|
|
|
}
|
|
}
|
|
@@ -262,38 +222,48 @@ impl TemplateWorkspace {
|
|
|
|
|
|
|
|
let mut menus = HashMap::new();
|
|
let mut menus = HashMap::new();
|
|
|
|
|
|
|
|
- for entry in entries.iter() {
|
|
|
|
|
- let entry = entry.borrow();
|
|
|
|
|
|
|
+ for id in self.roots.iter() {
|
|
|
|
|
+ let Some(entry) = self.indexes.get(&id) else {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ };
|
|
|
|
|
|
|
|
- match &*entry {
|
|
|
|
|
|
|
+ match entry {
|
|
|
WorkspaceNode::Collection(col) => {
|
|
WorkspaceNode::Collection(col) => {
|
|
|
let entry = &col.entry.entry;
|
|
let entry = &col.entry.entry;
|
|
|
menus.insert(
|
|
menus.insert(
|
|
|
entry.id,
|
|
entry.id,
|
|
|
WorkspaceEntryActionMenu::new(entry.id, entry.name.clone()),
|
|
WorkspaceEntryActionMenu::new(entry.id, entry.name.clone()),
|
|
|
);
|
|
);
|
|
|
- Self::index_menus(&mut menus, col);
|
|
|
|
|
|
|
+ self.index_menus(&mut menus, &col);
|
|
|
}
|
|
}
|
|
|
WorkspaceNode::Request(_) => {}
|
|
WorkspaceNode::Request(_) => {}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- self.entries = roots;
|
|
|
|
|
self.menus = menus;
|
|
self.menus = menus;
|
|
|
|
|
|
|
|
tracing::debug!("Loaded workspace: {self:#?}");
|
|
tracing::debug!("Loaded workspace: {self:#?}");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- fn index_menus(menus: &mut HashMap<i64, WorkspaceEntryActionMenu>, col: &CollectionNode) {
|
|
|
|
|
|
|
+ fn index_menus(
|
|
|
|
|
+ &self,
|
|
|
|
|
+ menus: &mut HashMap<i64, WorkspaceEntryActionMenu>,
|
|
|
|
|
+ col: &CollectionNode,
|
|
|
|
|
+ ) {
|
|
|
let entry = &col.entry.entry;
|
|
let entry = &col.entry.entry;
|
|
|
menus.insert(
|
|
menus.insert(
|
|
|
entry.id,
|
|
entry.id,
|
|
|
WorkspaceEntryActionMenu::new(entry.id, entry.name.clone()),
|
|
WorkspaceEntryActionMenu::new(entry.id, entry.name.clone()),
|
|
|
);
|
|
);
|
|
|
- for entry in col.entries.iter() {
|
|
|
|
|
- match &*entry.borrow() {
|
|
|
|
|
|
|
+
|
|
|
|
|
+ for id in col.entries.iter() {
|
|
|
|
|
+ let Some(entry) = self.indexes.get(&id) else {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ match entry {
|
|
|
WorkspaceNode::Collection(col) => {
|
|
WorkspaceNode::Collection(col) => {
|
|
|
- Self::index_menus(menus, col);
|
|
|
|
|
|
|
+ self.index_menus(menus, &col);
|
|
|
}
|
|
}
|
|
|
WorkspaceNode::Request(_) => {}
|
|
WorkspaceNode::Request(_) => {}
|
|
|
}
|
|
}
|
|
@@ -320,8 +290,6 @@ pub struct TemplateEnvironmentVariable {
|
|
|
pub secret: bool,
|
|
pub secret: bool,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-pub type RCell<T> = Rc<RefCell<T>>;
|
|
|
|
|
-
|
|
|
|
|
#[derive(Debug)]
|
|
#[derive(Debug)]
|
|
|
enum WorkspaceNode {
|
|
enum WorkspaceNode {
|
|
|
Collection(CollectionNode),
|
|
Collection(CollectionNode),
|
|
@@ -335,32 +303,20 @@ impl WorkspaceNode {
|
|
|
WorkspaceNode::Request(r) => &r.request.entry,
|
|
WorkspaceNode::Request(r) => &r.request.entry,
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- fn set_parent(&mut self, parent: RCell<WorkspaceNode>) {
|
|
|
|
|
- match self {
|
|
|
|
|
- WorkspaceNode::Collection(col) => col.parent = Some(parent),
|
|
|
|
|
- WorkspaceNode::Request(req) => req.parent = Some(parent),
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- fn children(&self) -> &[RCell<WorkspaceNode>] {
|
|
|
|
|
- match self {
|
|
|
|
|
- WorkspaceNode::Collection(col) => col.entries.iter().as_slice(),
|
|
|
|
|
- WorkspaceNode::Request(_) => &[],
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
#[derive(Debug)]
|
|
|
struct CollectionNode {
|
|
struct CollectionNode {
|
|
|
entry: WorkspaceCollection,
|
|
entry: WorkspaceCollection,
|
|
|
- parent: Option<RCell<WorkspaceNode>>,
|
|
|
|
|
- entries: Vec<RCell<WorkspaceNode>>,
|
|
|
|
|
|
|
+
|
|
|
|
|
+ /// ID of the parent node.
|
|
|
|
|
+ parent: Option<i64>,
|
|
|
|
|
+ entries: Vec<i64>,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
#[derive(Debug)]
|
|
|
struct RequestNode {
|
|
struct RequestNode {
|
|
|
- parent: Option<RCell<WorkspaceNode>>,
|
|
|
|
|
|
|
+ parent: Option<i64>,
|
|
|
request: WorkspaceRequest,
|
|
request: WorkspaceRequest,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -372,235 +328,3 @@ impl WorkspaceNode {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
-/// A fully deconstructed URL from a template request.
|
|
|
|
|
-/// Used as an intermediate step for populating the final URL with variables.
|
|
|
|
|
-#[derive(Debug)]
|
|
|
|
|
-pub struct TemplateRequestUrl<'a> {
|
|
|
|
|
- /// The URL scheme, e.g. `http`.
|
|
|
|
|
- pub scheme: &'a str,
|
|
|
|
|
-
|
|
|
|
|
- /// The URL host, includes the port if specified.
|
|
|
|
|
- pub host: &'a str,
|
|
|
|
|
-
|
|
|
|
|
- /// The URL path segments.
|
|
|
|
|
- ///
|
|
|
|
|
- /// All segments will be formatted as `/segment`, meaning empty Static
|
|
|
|
|
- /// fields represent a `/`, which is usually trailing.
|
|
|
|
|
- pub path: Vec<Segment<'a>>,
|
|
|
|
|
-
|
|
|
|
|
- /// Query parameters.
|
|
|
|
|
- pub query_params: Vec<(&'a str, &'a str)>,
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-impl<'a> TemplateRequestUrl<'a> {
|
|
|
|
|
- pub fn parse(input: &'a str) -> Result<Self, nom::Err<nom::error::Error<&'a str>>> {
|
|
|
|
|
- let (input, scheme) = take_while1(char::is_alphabetic)(input)?;
|
|
|
|
|
-
|
|
|
|
|
- let (input, _) = tag("://")(input)?;
|
|
|
|
|
-
|
|
|
|
|
- let mut path_parser = many0(preceded(
|
|
|
|
|
- char('/'),
|
|
|
|
|
- take_while(|c: char| c.is_ascii_alphanumeric() || c == ':'),
|
|
|
|
|
- ));
|
|
|
|
|
-
|
|
|
|
|
- let result = take_until1::<_, _, nom::error::Error<_>>("?")(input);
|
|
|
|
|
- match result {
|
|
|
|
|
- // URL has query parameters
|
|
|
|
|
- Ok((query, path)) => {
|
|
|
|
|
- // Parse query
|
|
|
|
|
- // First char will always be a '?' since we parsed succesfully
|
|
|
|
|
- let mut query = &query[1..];
|
|
|
|
|
- let mut query_params = vec![];
|
|
|
|
|
-
|
|
|
|
|
- loop {
|
|
|
|
|
- if query.is_empty() {
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- let (i, params) = separated_pair(
|
|
|
|
|
- take_while(|c: char| c != '='),
|
|
|
|
|
- char('='),
|
|
|
|
|
- take_while(|c: char| c != '&'),
|
|
|
|
|
- )
|
|
|
|
|
- .parse(query)?;
|
|
|
|
|
-
|
|
|
|
|
- query = i;
|
|
|
|
|
- query_params.push((params.0, params.1));
|
|
|
|
|
-
|
|
|
|
|
- if let Ok((i, _)) = char::<_, nom::error::Error<_>>('&').parse(query) {
|
|
|
|
|
- query = i;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- debug_assert!(query.is_empty());
|
|
|
|
|
-
|
|
|
|
|
- // Check path segments
|
|
|
|
|
-
|
|
|
|
|
- match take_until::<_, _, nom::error::Error<_>>("/")(path) {
|
|
|
|
|
- // Path exists
|
|
|
|
|
- Ok((path, host)) => {
|
|
|
|
|
- let (input, segments) = path_parser.parse(path)?;
|
|
|
|
|
- debug_assert!(input.is_empty());
|
|
|
|
|
- Ok(TemplateRequestUrl {
|
|
|
|
|
- scheme,
|
|
|
|
|
- host,
|
|
|
|
|
- path: segments
|
|
|
|
|
- .into_iter()
|
|
|
|
|
- .map(|segment| {
|
|
|
|
|
- segment
|
|
|
|
|
- .strip_prefix(':')
|
|
|
|
|
- .map_or(Segment::Static(segment), Segment::Dynamic)
|
|
|
|
|
- })
|
|
|
|
|
- .collect(),
|
|
|
|
|
- query_params,
|
|
|
|
|
- })
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // No path segments
|
|
|
|
|
- Err(_) => Ok(TemplateRequestUrl {
|
|
|
|
|
- scheme,
|
|
|
|
|
- host: path,
|
|
|
|
|
- path: vec![],
|
|
|
|
|
- query_params,
|
|
|
|
|
- }),
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- // No query params
|
|
|
|
|
- Err(_) => {
|
|
|
|
|
- match take_until::<_, _, nom::error::Error<_>>("/")(input) {
|
|
|
|
|
- // Path exists
|
|
|
|
|
- Ok((path, host)) => {
|
|
|
|
|
- let (input, segments) = path_parser.parse(path)?;
|
|
|
|
|
- debug_assert!(input.is_empty());
|
|
|
|
|
- Ok(TemplateRequestUrl {
|
|
|
|
|
- scheme,
|
|
|
|
|
- host,
|
|
|
|
|
- path: segments
|
|
|
|
|
- .into_iter()
|
|
|
|
|
- .map(|segment| {
|
|
|
|
|
- segment
|
|
|
|
|
- .strip_prefix(':')
|
|
|
|
|
- .map_or(Segment::Static(segment), Segment::Dynamic)
|
|
|
|
|
- })
|
|
|
|
|
- .collect(),
|
|
|
|
|
- query_params: vec![],
|
|
|
|
|
- })
|
|
|
|
|
- }
|
|
|
|
|
- // No path segments
|
|
|
|
|
- Err(_) => Ok(TemplateRequestUrl {
|
|
|
|
|
- scheme,
|
|
|
|
|
- host: input,
|
|
|
|
|
- path: vec![],
|
|
|
|
|
- query_params: vec![],
|
|
|
|
|
- }),
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-#[derive(Debug, PartialEq, Eq)]
|
|
|
|
|
-pub enum Segment<'a> {
|
|
|
|
|
- /// Path segments that do not change.
|
|
|
|
|
- /// The value is the final path value.
|
|
|
|
|
- Static(&'a str),
|
|
|
|
|
-
|
|
|
|
|
- /// Path segments that depend on request configuration.
|
|
|
|
|
- /// The value is the name of the variable in the request configuration
|
|
|
|
|
- /// that contains the final path value.
|
|
|
|
|
- Dynamic(&'a str),
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-#[cfg(test)]
|
|
|
|
|
-mod tests {
|
|
|
|
|
- use super::{Segment, TemplateRequestUrl};
|
|
|
|
|
-
|
|
|
|
|
- #[test]
|
|
|
|
|
- fn parses_path_placeholders() {
|
|
|
|
|
- let input = "http://localhost:4000/foo/:bar/bax";
|
|
|
|
|
-
|
|
|
|
|
- let expected_path = vec![
|
|
|
|
|
- Segment::Static("foo"),
|
|
|
|
|
- Segment::Dynamic("bar"),
|
|
|
|
|
- Segment::Static("bax"),
|
|
|
|
|
- ];
|
|
|
|
|
-
|
|
|
|
|
- let url = TemplateRequestUrl::parse(input).unwrap();
|
|
|
|
|
-
|
|
|
|
|
- assert_eq!("http", url.scheme);
|
|
|
|
|
- assert_eq!("localhost:4000", url.host);
|
|
|
|
|
- assert_eq!(expected_path, url.path);
|
|
|
|
|
- assert!(url.query_params.is_empty());
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- #[test]
|
|
|
|
|
- fn parses_path_placeholders_trailing_slash() {
|
|
|
|
|
- let input = "http://localhost:4000/foo/:bar/bax/";
|
|
|
|
|
-
|
|
|
|
|
- let expected_path = vec![
|
|
|
|
|
- Segment::Static("foo"),
|
|
|
|
|
- Segment::Dynamic("bar"),
|
|
|
|
|
- Segment::Static("bax"),
|
|
|
|
|
- Segment::Static(""),
|
|
|
|
|
- ];
|
|
|
|
|
-
|
|
|
|
|
- let url = TemplateRequestUrl::parse(input).unwrap();
|
|
|
|
|
-
|
|
|
|
|
- assert_eq!("http", url.scheme);
|
|
|
|
|
- assert_eq!("localhost:4000", url.host);
|
|
|
|
|
- assert_eq!(expected_path, url.path);
|
|
|
|
|
- assert!(url.query_params.is_empty());
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- #[test]
|
|
|
|
|
- fn parses_no_path_segments() {
|
|
|
|
|
- let input = "http://localhost:4000";
|
|
|
|
|
-
|
|
|
|
|
- let url = TemplateRequestUrl::parse(input).unwrap();
|
|
|
|
|
-
|
|
|
|
|
- assert_eq!("http", url.scheme);
|
|
|
|
|
- assert_eq!("localhost:4000", url.host);
|
|
|
|
|
- assert!(url.path.is_empty());
|
|
|
|
|
- assert!(url.query_params.is_empty());
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- #[test]
|
|
|
|
|
- fn parse_no_path_segments_trailing_slash() {
|
|
|
|
|
- let input = "http://localhost:4000/";
|
|
|
|
|
-
|
|
|
|
|
- let url = TemplateRequestUrl::parse(input).unwrap();
|
|
|
|
|
-
|
|
|
|
|
- assert_eq!("http", url.scheme);
|
|
|
|
|
- assert_eq!("localhost:4000", url.host);
|
|
|
|
|
- assert_eq!(vec![Segment::Static("")], url.path);
|
|
|
|
|
- assert!(url.query_params.is_empty());
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- #[test]
|
|
|
|
|
- fn parse_query_params_no_path() {
|
|
|
|
|
- let input = "http://localhost:4000?foo=bar&baz=bax";
|
|
|
|
|
-
|
|
|
|
|
- let url = TemplateRequestUrl::parse(input).unwrap();
|
|
|
|
|
-
|
|
|
|
|
- assert_eq!("http", url.scheme);
|
|
|
|
|
- assert_eq!("localhost:4000", url.host);
|
|
|
|
|
- assert!(url.path.is_empty());
|
|
|
|
|
- assert_eq!(vec![("foo", "bar"), ("baz", "bax")], url.query_params);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- #[test]
|
|
|
|
|
- fn parse_query_params_with_path() {
|
|
|
|
|
- let input = "http://localhost:4000/foo/:bar?foo=bar&baz=bax";
|
|
|
|
|
-
|
|
|
|
|
- let url = TemplateRequestUrl::parse(input).unwrap();
|
|
|
|
|
-
|
|
|
|
|
- assert_eq!("http", url.scheme);
|
|
|
|
|
- assert_eq!("localhost:4000", url.host);
|
|
|
|
|
- assert_eq!(
|
|
|
|
|
- vec![Segment::Static("foo"), Segment::Dynamic("bar")],
|
|
|
|
|
- url.path
|
|
|
|
|
- );
|
|
|
|
|
- assert_eq!(vec![("foo", "bar"), ("baz", "bax")], url.query_params);
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|