.
This commit is contained in:
+208
-65
@@ -1,4 +1,4 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
|
||||
use std::ops::Sub;
|
||||
use crate::navigator::Navigator;
|
||||
use crate::rdf::term_helper::{TermHelper, TermHelperMut};
|
||||
@@ -12,7 +12,7 @@ use iced::keyboard::{Event, key};
|
||||
use iced::widget::button::Style;
|
||||
use iced::widget::{button, center, column, combo_box, container, grid, mouse_area, opaque, operation, pick_list, row, scrollable, space, stack, table, text, text_input, toggler};
|
||||
use iced::window::Settings;
|
||||
use iced::{Background, Color, Element, Length, Subscription, Task, color, keyboard, window};
|
||||
use iced::{color, keyboard, window, Background, Color, Element, Font, Length, Subscription, Task, font};
|
||||
use iced::advanced::text::Wrapping;
|
||||
use iced::widget::grid::Sizing;
|
||||
use ldp::middleware::BasicAuthMiddleware;
|
||||
@@ -21,7 +21,7 @@ use ldp::reqwest::{Client, Url};
|
||||
use ldp::reqwest_middleware::{ClientBuilder, ClientWithMiddleware};
|
||||
use ldp::{RdfSource, RdfSourceUpdateResponse, ResourceRequestBuilder, SerializationOptions};
|
||||
use oxigraph::io::RdfFormat;
|
||||
use oxigraph::model::{BaseDirection, Graph, NamedNode, NamedOrBlankNode, NamedOrBlankNodeRef, Quad, Term, TermRef, Triple, TripleRef};
|
||||
use oxigraph::model::{BaseDirection, Graph, NamedNode, NamedNodeRef, NamedOrBlankNode, NamedOrBlankNodeRef, Quad, Term, TermRef, Triple, TripleRef};
|
||||
use tracing::{debug, error, trace};
|
||||
use gl_graph::class::Class;
|
||||
use gl_graph::ontology::{ConnectedOntology, OntologyBuilder, ReadOnlyEntity, ResourceDescription};
|
||||
@@ -77,6 +77,7 @@ pub(crate) enum Message {
|
||||
RunInference,
|
||||
SetInferredTriples(Graph),
|
||||
ShowInferencesWindow,
|
||||
ShowReadOnlyWindow,
|
||||
Event(Event),
|
||||
}
|
||||
|
||||
@@ -110,6 +111,7 @@ pub(crate) struct Publisher {
|
||||
resource_descriptions: HashMap<NamedNode, ResourceDescription>,
|
||||
window_id: window::Id,
|
||||
inference_window_id: Option<window::Id>,
|
||||
read_only_window_id: Option<window::Id>,
|
||||
url_input: String,
|
||||
url_input_valid: bool,
|
||||
navigator: Navigator<Url>,
|
||||
@@ -167,6 +169,7 @@ impl Publisher {
|
||||
resource_descriptions: HashMap::new(),
|
||||
window_id: id,
|
||||
inference_window_id: None,
|
||||
read_only_window_id: None,
|
||||
url_input: starting_url.to_string(),
|
||||
url_input_valid: true,
|
||||
navigator,
|
||||
@@ -237,8 +240,10 @@ impl Publisher {
|
||||
Message::WindowClosed(id) => {
|
||||
if self.window_id == id {
|
||||
task = iced::exit();
|
||||
} else {
|
||||
} else if let Some(search_state) = &self.search_state && search_state.window_id == id {
|
||||
self.search_state = None;
|
||||
} else if let Some(infrence_window_id) = self.inference_window_id && infrence_window_id == id {
|
||||
self.inference_window_id = None;
|
||||
}
|
||||
}
|
||||
Message::URLInputChanged(value) => {
|
||||
@@ -633,6 +638,15 @@ impl Publisher {
|
||||
task = window_task.map(|_| Message::None);
|
||||
}
|
||||
}
|
||||
Message::ShowReadOnlyWindow => {
|
||||
if let Some(id) = self.read_only_window_id {
|
||||
task = window::gain_focus(id);
|
||||
} else {
|
||||
let (id, window_task) = window::open(Settings::default());
|
||||
self.read_only_window_id = Some(id);
|
||||
task = window_task.map(|_| Message::None);
|
||||
}
|
||||
}
|
||||
Message::Event(Event::KeyPressed {
|
||||
key: keyboard::Key::Named(key::Named::Tab),
|
||||
modifiers,
|
||||
@@ -654,6 +668,8 @@ impl Publisher {
|
||||
"Graph of Liberty Publisher - Inferences".to_string()
|
||||
} else if let Some(search_state) = &self.search_state && window == search_state.window_id {
|
||||
"Graph of Liberty Publisher - Search".to_string()
|
||||
} else if let Some(read_only_window_id) = &self.read_only_window_id {
|
||||
"Graph of Liberty Publisher - Read Only Triples".to_string()
|
||||
} else {
|
||||
"Graph of Liberty Publisher".to_string()
|
||||
}
|
||||
@@ -666,6 +682,186 @@ impl Publisher {
|
||||
])
|
||||
}
|
||||
|
||||
fn view_inferred_class_table<'a>(&'a self, triples: impl IntoIterator<Item = TripleRef<'a>>) -> Element<'a, Message> {
|
||||
let bold = |t| text(t).font(Font { weight: font::Weight::Bold, ..Font::default() });
|
||||
|
||||
let subject_column = table::column(bold("Subject"), |(subject, _): (NamedNodeRef, _)| {
|
||||
text(self.curie_helper
|
||||
.abbreviate(Some(self.document.origin()), subject.as_str())
|
||||
.unwrap_or_else(|| subject.as_str().to_string()))
|
||||
});
|
||||
|
||||
let class_column = table::column(bold("Classes"), |(_, classes): (_, BTreeSet<NamedNodeRef>)| {
|
||||
let textual_classes = classes.iter()
|
||||
.map(|class| {
|
||||
let curie = self.curie_helper
|
||||
.abbreviate(Some(self.document.origin()), class.as_str())
|
||||
.unwrap_or_else(|| class.as_str().to_string());
|
||||
|
||||
let label = self.resource_descriptions
|
||||
.get(&class.into_owned())
|
||||
.and_then(|description| description.label.clone());
|
||||
|
||||
if let Some(label) = label {
|
||||
format!("{curie} ({label})")
|
||||
} else {
|
||||
curie
|
||||
}
|
||||
}).collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
|
||||
text(textual_classes)
|
||||
});
|
||||
|
||||
let mut inferred_classes: BTreeMap<NamedNodeRef, BTreeSet<NamedNodeRef>> = BTreeMap::new();
|
||||
for triple in triples {
|
||||
if let NamedOrBlankNodeRef::NamedNode(subject) = triple.subject &&
|
||||
let TermRef::NamedNode(class) = triple.object {
|
||||
inferred_classes.entry(subject)
|
||||
.and_modify(|classes| { classes.insert(class); })
|
||||
.or_insert(BTreeSet::from_iter([class]));
|
||||
}
|
||||
}
|
||||
|
||||
table([subject_column, class_column], inferred_classes).into()
|
||||
}
|
||||
|
||||
fn view_triple_table<'a>(&'a self, triples: impl IntoIterator<Item = TripleRef<'a>>) -> Element<'a, Message> {
|
||||
let bold = |t| text(t).font(Font { weight: font::Weight::Bold, ..Font::default() });
|
||||
|
||||
let subject_column = table::column(bold("Subject"), |triple: TripleRef| {
|
||||
let curie = if let NamedOrBlankNodeRef::NamedNode(subject) = triple.subject {
|
||||
Some(self.curie_helper
|
||||
.abbreviate(Some(self.document.origin()), subject.as_str())
|
||||
.unwrap_or_else(|| subject.as_str().to_string()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
curie.map(text)
|
||||
});
|
||||
|
||||
let predicate_column = table::column(bold("Predicate"), |triple: TripleRef| {
|
||||
let curie = self.curie_helper
|
||||
.abbreviate(None, triple.predicate.as_str())
|
||||
.unwrap_or_else(|| triple.predicate.as_str().to_string());
|
||||
|
||||
let label = self.resource_descriptions
|
||||
.get(&triple.predicate.into_owned())
|
||||
.and_then(|description| description.label.clone());
|
||||
|
||||
if let Some(label) = label {
|
||||
text(format!("{curie} ({label})"))
|
||||
} else {
|
||||
text(curie)
|
||||
}
|
||||
});
|
||||
|
||||
let object_column = table::column(bold("Object"), |triple: TripleRef| {
|
||||
match triple.object {
|
||||
TermRef::NamedNode(node) => {
|
||||
let curie = self.curie_helper
|
||||
.abbreviate(Some(self.document.origin()), node.as_str())
|
||||
.unwrap_or_else(|| node.as_str().to_string());
|
||||
|
||||
let label = self.resource_descriptions
|
||||
.get(&node.into_owned())
|
||||
.and_then(|description| description.label.clone());
|
||||
|
||||
if let Some(label) = label {
|
||||
text(format!("{curie} ({label})"))
|
||||
} else {
|
||||
text(curie)
|
||||
}
|
||||
}
|
||||
_ => text(triple.object.to_string()),
|
||||
}
|
||||
});
|
||||
|
||||
table([subject_column, predicate_column, object_column], triples).into()
|
||||
}
|
||||
|
||||
fn view_search_window<'a>(&'a self, search_state: &'a SearchState) -> Element<'a, Message> {
|
||||
let search_input = text_input("Query", &search_state.query)
|
||||
.on_input(Message::QueryUpdated)
|
||||
.id("query");
|
||||
|
||||
let type_selector = pick_list(
|
||||
search_state.entity_class_selection.as_ref(),
|
||||
Class::ALL,
|
||||
ToString::to_string,
|
||||
).on_select(Message::UpdateQueryClass);
|
||||
|
||||
let mut columns = vec![table::column(text("CURIE"), |document: &HashMap<gl_search::Field, gl_search::OwnedValue>| {
|
||||
let iri = document.get(&Schema::iri_field())
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default();
|
||||
|
||||
let abbreviated_iri = document.get(&Schema::curie_field())
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default();
|
||||
|
||||
button(text(abbreviated_iri).wrapping(Wrapping::Word))
|
||||
.on_press(Message::SearchResultClicked(NamedNode::new_unchecked(iri)))
|
||||
.style(button::text)
|
||||
})];
|
||||
|
||||
let fields = search_state.entity_class_selection
|
||||
.as_ref()
|
||||
.map(|class| {
|
||||
class.fields(language::ENGLISH_TAG.clone())
|
||||
}).unwrap_or_default();
|
||||
|
||||
for (field, label) in fields {
|
||||
columns.push(
|
||||
table::column(text(label), move |document: &HashMap<gl_search::Field, gl_search::OwnedValue>| {
|
||||
let iri = document.get(&Schema::iri_field())
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default();
|
||||
|
||||
let value = document
|
||||
.get(&field)
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default();
|
||||
|
||||
button(text(value).wrapping(Wrapping::Word))
|
||||
.on_press(Message::SearchResultClicked(NamedNode::new_unchecked(iri)))
|
||||
.style(button::text)
|
||||
})
|
||||
.width(Length::Fixed(256.0)),
|
||||
);
|
||||
}
|
||||
|
||||
let results_table = if columns.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(scrollable(table(columns, &search_state.results)))
|
||||
};
|
||||
|
||||
column![row![search_input, type_selector], results_table].into()
|
||||
}
|
||||
|
||||
fn view_inference_window(&self) -> Element<'_, Message> {
|
||||
let classes = self.inferred_triples.triples_for_predicate(vocab::rdf::TYPE);
|
||||
|
||||
column![
|
||||
self.view_inferred_class_table(classes),
|
||||
scrollable(self.view_triple_table(&self.inferred_triples)),
|
||||
].into()
|
||||
}
|
||||
|
||||
fn view_read_only_window(&self) -> Element<'_, Message> {
|
||||
let read_only_triples = self.document
|
||||
.dataset()
|
||||
.iter_both()
|
||||
.filter_map(|(_, quad, state)| if state.read_only { Some(quad) } else { None })
|
||||
.map(|quad| TripleRef::from(quad.as_ref()));
|
||||
|
||||
column![
|
||||
scrollable(self.view_triple_table(read_only_triples)),
|
||||
].into()
|
||||
}
|
||||
|
||||
fn view_row<'a>(
|
||||
&'a self,
|
||||
key: QuadKey,
|
||||
@@ -835,66 +1031,6 @@ impl Publisher {
|
||||
.into()
|
||||
}
|
||||
|
||||
fn view_search_window<'a>(&'a self, search_state: &'a SearchState) -> Element<'a, Message> {
|
||||
let search_input = text_input("Query", &search_state.query)
|
||||
.on_input(Message::QueryUpdated)
|
||||
.id("query");
|
||||
|
||||
let type_selector = pick_list(
|
||||
search_state.entity_class_selection.as_ref(),
|
||||
Class::ALL,
|
||||
ToString::to_string,
|
||||
).on_select(Message::UpdateQueryClass);
|
||||
|
||||
let mut columns = vec![table::column(text("CURIE"), |document: &HashMap<gl_search::Field, gl_search::OwnedValue>| {
|
||||
let iri = document.get(&Schema::iri_field())
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default();
|
||||
|
||||
let abbreviated_iri = document.get(&Schema::curie_field())
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default();
|
||||
|
||||
button(text(abbreviated_iri).wrapping(Wrapping::Word))
|
||||
.on_press(Message::SearchResultClicked(NamedNode::new_unchecked(iri)))
|
||||
.style(button::text)
|
||||
})];
|
||||
|
||||
let fields = search_state.entity_class_selection
|
||||
.as_ref()
|
||||
.map(|class| {
|
||||
class.fields(language::ENGLISH_TAG.clone())
|
||||
}).unwrap_or_default();
|
||||
|
||||
for (field, label) in fields {
|
||||
columns.push(
|
||||
table::column(text(label), move |document: &HashMap<gl_search::Field, gl_search::OwnedValue>| {
|
||||
let iri = document.get(&Schema::iri_field())
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default();
|
||||
|
||||
let value = document
|
||||
.get(&field)
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_default();
|
||||
|
||||
button(text(value).wrapping(Wrapping::Word))
|
||||
.on_press(Message::SearchResultClicked(NamedNode::new_unchecked(iri)))
|
||||
.style(button::text)
|
||||
})
|
||||
.width(Length::Fixed(256.0)),
|
||||
);
|
||||
}
|
||||
|
||||
let results_table = if columns.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(scrollable(table(columns, &search_state.results)))
|
||||
};
|
||||
|
||||
column![row![search_input, type_selector], results_table].into()
|
||||
}
|
||||
|
||||
pub(crate) fn view(&self, window: window::Id) -> Element<'_, Message> {
|
||||
if let Some(search_state) = &self.search_state &&
|
||||
search_state.window_id == window {
|
||||
@@ -902,10 +1038,15 @@ impl Publisher {
|
||||
}
|
||||
|
||||
if let Some(inference_window_id) = self.inference_window_id && window == inference_window_id {
|
||||
return crate::window::inferences::view_inference_window(&self.resource_descriptions, self.document.origin(), &self.curie_helper, &self.inferred_triples);
|
||||
return self.view_inference_window();
|
||||
}
|
||||
|
||||
if let Some(read_only_window_id) = self.read_only_window_id && window == read_only_window_id {
|
||||
return self.view_read_only_window();
|
||||
}
|
||||
|
||||
let show_inferences_button = button("\u{1f9e0}").on_press(Message::ShowInferencesWindow);
|
||||
let show_read_only_button = button("\u{1f6ab}").on_press(Message::ShowReadOnlyWindow);
|
||||
let back_button = button("\u{1f870}").on_press(Message::NavigateBack);
|
||||
let forward_button = button("\u{1f872}").on_press(Message::NavigateForward);
|
||||
let new_document_button = button("New Document").on_press(Message::ShowNewDocumentButtons);
|
||||
@@ -923,6 +1064,7 @@ impl Publisher {
|
||||
rows = self.document
|
||||
.dataset()
|
||||
.iter_both()
|
||||
.filter(|(_, _, state)| !state.read_only)
|
||||
.map(|(key, quad, state)| self.view_row(key, quad, state))
|
||||
.collect();
|
||||
|
||||
@@ -955,6 +1097,7 @@ impl Publisher {
|
||||
let content = navigation_area(column![
|
||||
row![
|
||||
show_inferences_button,
|
||||
show_read_only_button,
|
||||
new_document_button,
|
||||
add_row_button,
|
||||
back_button,
|
||||
|
||||
@@ -6,7 +6,6 @@ mod rdf;
|
||||
mod theme;
|
||||
mod widget;
|
||||
mod tasks;
|
||||
pub mod window;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use crate::app::Publisher;
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
use std::collections::{BTreeMap, BTreeSet, HashMap};
|
||||
use iced::{font, Element, Font};
|
||||
use iced::widget::{column, scrollable, span, table, text};
|
||||
use oxigraph::model::{Graph, NamedNode, NamedNodeRef, NamedOrBlankNodeRef, TermRef, TripleRef};
|
||||
use url::Url;
|
||||
use gl_graph::{vocab, CurieHelper};
|
||||
use gl_graph::ontology::ResourceDescription;
|
||||
use crate::app::Message;
|
||||
|
||||
pub(crate) fn view_inference_window<'a>(resource_descriptions: &HashMap<NamedNode, ResourceDescription>, origin: &Url, curie_helper: &CurieHelper, inferences: &'a Graph) -> Element<'a, Message> {
|
||||
let bold = |t| text(t).font(Font { weight: font::Weight::Bold, ..Font::default() });
|
||||
|
||||
let inferred_class_subject_column = table::column(bold("Subject"), |(subject, _): (NamedNodeRef, _)| {
|
||||
text(curie_helper
|
||||
.abbreviate(Some(origin), subject.as_str())
|
||||
.unwrap_or_else(|| subject.as_str().to_string()))
|
||||
});
|
||||
|
||||
let inferred_class_class_column = table::column(bold("Classes"), |(_, classes): (_, BTreeSet<NamedNodeRef>)| {
|
||||
let textual_classes = classes.iter()
|
||||
.map(|class| {
|
||||
let curie = curie_helper
|
||||
.abbreviate(Some(origin), class.as_str())
|
||||
.unwrap_or_else(|| class.as_str().to_string());
|
||||
|
||||
let label = resource_descriptions
|
||||
.get(&class.into_owned())
|
||||
.and_then(|description| description.label.clone());
|
||||
|
||||
if let Some(label) = label {
|
||||
format!("{curie} ({label})")
|
||||
} else {
|
||||
curie
|
||||
}
|
||||
}).collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
|
||||
text(textual_classes)
|
||||
});
|
||||
|
||||
let mut inferred_classes: BTreeMap<NamedNodeRef, BTreeSet<NamedNodeRef>> = BTreeMap::new();
|
||||
for triple in inferences.triples_for_predicate(vocab::rdf::TYPE) {
|
||||
if let NamedOrBlankNodeRef::NamedNode(subject) = triple.subject && let TermRef::NamedNode(class) = triple.object {
|
||||
inferred_classes.entry(subject)
|
||||
.and_modify(|classes| { classes.insert(class); })
|
||||
.or_insert(BTreeSet::from_iter([class]));
|
||||
}
|
||||
}
|
||||
let inferred_class_table = table([inferred_class_subject_column, inferred_class_class_column], inferred_classes);
|
||||
|
||||
let subject_column = table::column(bold("Subject"), |triple: TripleRef| {
|
||||
let curie = if let NamedOrBlankNodeRef::NamedNode(subject) = triple.subject {
|
||||
Some(curie_helper
|
||||
.abbreviate(Some(origin), subject.as_str())
|
||||
.unwrap_or_else(|| subject.as_str().to_string()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
curie.map(text)
|
||||
});
|
||||
|
||||
let predicate_column = table::column(bold("Predicate"), |triple: TripleRef| {
|
||||
let curie = curie_helper
|
||||
.abbreviate(None, triple.predicate.as_str())
|
||||
.unwrap_or_else(|| triple.predicate.as_str().to_string());
|
||||
|
||||
let label = resource_descriptions
|
||||
.get(&triple.predicate.into_owned())
|
||||
.and_then(|description| description.label.clone());
|
||||
|
||||
if let Some(label) = label {
|
||||
text(format!("{curie} ({label})"))
|
||||
} else {
|
||||
text(curie)
|
||||
}
|
||||
});
|
||||
|
||||
let object_column = table::column(bold("Object"), |triple: TripleRef| {
|
||||
match triple.object {
|
||||
TermRef::NamedNode(node) => {
|
||||
let curie = curie_helper
|
||||
.abbreviate(Some(origin), node.as_str())
|
||||
.unwrap_or_else(|| node.as_str().to_string());
|
||||
|
||||
let label = resource_descriptions
|
||||
.get(&node.into_owned())
|
||||
.and_then(|description| description.label.clone());
|
||||
|
||||
if let Some(label) = label {
|
||||
text(format!("{curie} ({label})"))
|
||||
} else {
|
||||
text(curie)
|
||||
}
|
||||
}
|
||||
_ => text(triple.object.to_string()),
|
||||
}
|
||||
});
|
||||
|
||||
let all_inferences_table = table([subject_column, predicate_column, object_column], inferences);
|
||||
|
||||
column![
|
||||
inferred_class_table,
|
||||
scrollable(all_inferences_table),
|
||||
].into()
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
pub(crate) mod inferences;
|
||||
Reference in New Issue
Block a user