.
This commit is contained in:
@@ -10,7 +10,6 @@ gl-search.workspace = true
|
||||
|
||||
clap.workspace = true
|
||||
color-eyre.workspace = true
|
||||
csv = "1.4"
|
||||
http.workspace = true
|
||||
iced.workspace = true
|
||||
ldp.workspace = true
|
||||
|
||||
+371
-201
@@ -1,33 +1,46 @@
|
||||
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
|
||||
use std::ops::Sub;
|
||||
use crate::navigator::Navigator;
|
||||
use crate::rdf::conversion::term_to_named_node;
|
||||
use crate::rdf::term_helper::{TermHelper, TermHelperMut};
|
||||
use crate::tasks;
|
||||
use crate::widget::iri_input::iri_input;
|
||||
use crate::widget::navigation_area::navigation_area;
|
||||
use gl_graph::{language, CurieHelper, vocab};
|
||||
use gl_graph::class::Class;
|
||||
use gl_graph::ontology::{ConnectedOntology, OntologyBuilder, ReadOnlyEntity, ResourceDescription};
|
||||
use gl_graph::{CurieHelper, language, vocab};
|
||||
use gl_search::tantivy::schema::Value;
|
||||
use gl_search::{Schema, SearchIndex};
|
||||
use http::StatusCode;
|
||||
use iced::advanced::text::Wrapping;
|
||||
use iced::alignment::Horizontal;
|
||||
use iced::keyboard::{Event, key};
|
||||
use iced::task::sipper;
|
||||
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::{color, keyboard, window, Background, Color, Element, Font, Length, Subscription, Task, font};
|
||||
use iced::advanced::text::Wrapping;
|
||||
use iced::widget::grid::Sizing;
|
||||
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, Font, Length, Subscription, Task, color, font, keyboard, window,
|
||||
};
|
||||
use ldp::middleware::BasicAuthMiddleware;
|
||||
use ldp::model::{KeyedDataset, QuadKey};
|
||||
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, NamedNodeRef, NamedOrBlankNode, NamedOrBlankNodeRef, Quad, Term, TermRef, Triple, TripleRef};
|
||||
use oxigraph::model::{
|
||||
BaseDirection, Graph, NamedNode, NamedNodeRef, NamedOrBlankNode, NamedOrBlankNodeRef, Quad,
|
||||
Term, TermRef, Triple, TripleRef,
|
||||
};
|
||||
use rfd::FileHandle;
|
||||
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
|
||||
use std::ops::Sub;
|
||||
use std::path::PathBuf;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tonic::codegen::tokio_stream::StreamExt;
|
||||
use tracing::{debug, error, trace};
|
||||
use gl_graph::class::Class;
|
||||
use gl_graph::ontology::{ConnectedOntology, OntologyBuilder, ReadOnlyEntity, ResourceDescription};
|
||||
use gl_search::tantivy::schema::Value;
|
||||
use crate::rdf::conversion::term_to_named_node;
|
||||
use crate::tasks;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) enum Message {
|
||||
@@ -68,6 +81,9 @@ pub(crate) enum Message {
|
||||
ShowOverwriteConfirmationModal,
|
||||
HideOverwriteConfirmationModal,
|
||||
ConfirmOverwrite,
|
||||
DownloadResource,
|
||||
DownloadLocationSelected(Option<PathBuf>),
|
||||
DownloadProgress((usize, Option<usize>)),
|
||||
NewDocument(Class, Option<Url>),
|
||||
NavigateToPredicate(QuadKey),
|
||||
NavigateToObject(QuadKey),
|
||||
@@ -102,6 +118,11 @@ struct SearchState {
|
||||
results: Vec<HashMap<gl_search::Field, gl_search::OwnedValue>>,
|
||||
}
|
||||
|
||||
enum DownloadState {
|
||||
Idle,
|
||||
InProgress(FileHandle, usize),
|
||||
}
|
||||
|
||||
pub(crate) struct Publisher {
|
||||
http_client: ClientWithMiddleware,
|
||||
curie_helper: CurieHelper,
|
||||
@@ -116,6 +137,7 @@ pub(crate) struct Publisher {
|
||||
url_input_valid: bool,
|
||||
navigator: Navigator<Url>,
|
||||
document: RdfSource<KeyedDataset<RowState>>,
|
||||
download_state: DownloadState,
|
||||
inferred_triples: Graph,
|
||||
hovered_row: Option<QuadKey>,
|
||||
search_state: Option<SearchState>,
|
||||
@@ -129,11 +151,14 @@ pub(crate) struct Publisher {
|
||||
impl Publisher {
|
||||
fn is_read_only<'a>(&'a self, triple: impl Into<TripleRef<'a>>) -> bool {
|
||||
let triple = triple.into();
|
||||
if triple.predicate == vocab::rdf::TYPE &&
|
||||
let TermRef::NamedNode(node) = triple.object {
|
||||
self.read_only_entities.contains(&ReadOnlyEntity::Class(node.into_owned()))
|
||||
if triple.predicate == vocab::rdf::TYPE
|
||||
&& let TermRef::NamedNode(node) = triple.object
|
||||
{
|
||||
self.read_only_entities
|
||||
.contains(&ReadOnlyEntity::Class(node.into_owned()))
|
||||
} else {
|
||||
self.read_only_entities.contains(&ReadOnlyEntity::Property(triple.predicate.into_owned()))
|
||||
self.read_only_entities
|
||||
.contains(&ReadOnlyEntity::Property(triple.predicate.into_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,6 +199,7 @@ impl Publisher {
|
||||
url_input_valid: true,
|
||||
navigator,
|
||||
document,
|
||||
download_state: DownloadState::Idle,
|
||||
inferred_triples: Graph::new(),
|
||||
hovered_row: None,
|
||||
search_state: None,
|
||||
@@ -193,8 +219,9 @@ impl Publisher {
|
||||
|
||||
match message {
|
||||
Message::ConnectToOntologyService(endpoint) => {
|
||||
let builder = OntologyBuilder::from_string(&endpoint, language::ENGLISH_OR_UNTAGGED.clone())
|
||||
.expect("Unable to parse endpoint of ontology service");
|
||||
let builder =
|
||||
OntologyBuilder::from_string(&endpoint, language::ENGLISH_OR_UNTAGGED.clone())
|
||||
.expect("Unable to parse endpoint of ontology service");
|
||||
|
||||
task = tasks::connect_to_ontology_service(builder)
|
||||
.chain(Task::done(Message::PopulateCaches));
|
||||
@@ -204,9 +231,9 @@ impl Publisher {
|
||||
}
|
||||
Message::PopulateCaches => {
|
||||
if let Some(ontology) = &mut self.ontology {
|
||||
let classes = Class::ALL.iter().map(|class| {
|
||||
class.to_named_node().into_owned()
|
||||
});
|
||||
let classes = Class::ALL
|
||||
.iter()
|
||||
.map(|class| class.to_named_node().into_owned());
|
||||
|
||||
task = Task::batch([
|
||||
tasks::list_datatypes(ontology.clone()),
|
||||
@@ -216,18 +243,22 @@ impl Publisher {
|
||||
}
|
||||
}
|
||||
Message::CacheDatatypes(datatypes) => {
|
||||
self.abbreviated_datatypes = datatypes.keys()
|
||||
self.abbreviated_datatypes = datatypes
|
||||
.keys()
|
||||
.map(|node| {
|
||||
self.curie_helper.abbreviate(None, node.as_str())
|
||||
self.curie_helper
|
||||
.abbreviate(None, node.as_str())
|
||||
.unwrap_or(node.as_str().to_string())
|
||||
}).collect();
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
Message::CacheReadOnlyEntities(entities) => {
|
||||
self.read_only_entities = entities;
|
||||
}
|
||||
Message::LookupResourceDescriptions(nodes) => {
|
||||
let provided_nodes: HashSet<NamedNode> = HashSet::from_iter(nodes);
|
||||
let nodes_with_known_descriptions = HashSet::from_iter(self.resource_descriptions.keys().cloned());
|
||||
let nodes_with_known_descriptions =
|
||||
HashSet::from_iter(self.resource_descriptions.keys().cloned());
|
||||
let nodes_to_look_up = provided_nodes.sub(&nodes_with_known_descriptions);
|
||||
|
||||
if let Some(ontology) = &mut self.ontology {
|
||||
@@ -240,10 +271,18 @@ impl Publisher {
|
||||
Message::WindowClosed(id) => {
|
||||
if self.window_id == id {
|
||||
task = iced::exit();
|
||||
} else if let Some(search_state) = &self.search_state && search_state.window_id == id {
|
||||
} 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 {
|
||||
} else if let Some(infrence_window_id) = self.inference_window_id
|
||||
&& infrence_window_id == id
|
||||
{
|
||||
self.inference_window_id = None;
|
||||
} else if let Some(read_only_window_id) = self.read_only_window_id
|
||||
&& read_only_window_id == id
|
||||
{
|
||||
self.read_only_window_id = None;
|
||||
}
|
||||
}
|
||||
Message::URLInputChanged(value) => {
|
||||
@@ -259,47 +298,49 @@ impl Publisher {
|
||||
}
|
||||
}
|
||||
Message::NavigateTo(url) => {
|
||||
task = Task::done(Message::ResetState)
|
||||
.chain(Task::done(Message::FetchDocument(url)));
|
||||
task =
|
||||
Task::done(Message::ResetState).chain(Task::done(Message::FetchDocument(url)));
|
||||
}
|
||||
Message::FetchDocument(url) => {
|
||||
let client = self.http_client.clone();
|
||||
task = Task::future(async {
|
||||
let request = ResourceRequestBuilder::with_client_and_url(client, url)
|
||||
.accept_rdf_format(RdfFormat::Turtle)
|
||||
.follow_described_by(true)
|
||||
.build();
|
||||
task = Task::perform(
|
||||
async {
|
||||
let request = ResourceRequestBuilder::with_client_and_url(client, url)
|
||||
.accept_rdf_format(RdfFormat::Turtle)
|
||||
.follow_described_by(true)
|
||||
.build();
|
||||
|
||||
match request.send().await {
|
||||
Ok(resource) => match resource.into_rdf_source().await {
|
||||
Ok(rdf_source) => Message::LoadDocument(rdf_source),
|
||||
Err(err) => {
|
||||
Message::ShowError(format!("Unable to parse response: {err}"))
|
||||
}
|
||||
},
|
||||
Err(ldp::Error::Reqwest(err)) => {
|
||||
if err.status() == Some(StatusCode::NOT_FOUND) {
|
||||
Message::ResourceNotFound
|
||||
} else {
|
||||
Message::ShowError(err.to_string())
|
||||
}
|
||||
let response = request.send().await?;
|
||||
response.into_rdf_source().await
|
||||
},
|
||||
|result| match result {
|
||||
Ok(document) => Message::LoadDocument(document),
|
||||
Err(ldp::Error::Reqwest(err))
|
||||
if err.status() == Some(StatusCode::NOT_FOUND) =>
|
||||
{
|
||||
Message::ResourceNotFound
|
||||
}
|
||||
Err(err) => Message::ShowError(format!("Request failed: {err}")),
|
||||
}
|
||||
});
|
||||
Err(err) => Message::ShowError(err.to_string()),
|
||||
},
|
||||
);
|
||||
}
|
||||
Message::ResourceNotFound => {
|
||||
self.resource_not_found = true;
|
||||
task = Task::done(Message::ShowNewDocumentButtons);
|
||||
self.resource_not_found = true;
|
||||
}
|
||||
Message::LoadDocument(document) => {
|
||||
let nodes_to_look_up = document.dataset().quads.iter().flat_map(|(_, quad)| {
|
||||
let mut nodes = vec![ quad.predicate.clone() ];
|
||||
if let Some(object) = term_to_named_node(&quad.object) {
|
||||
nodes.push(object.clone());
|
||||
}
|
||||
nodes
|
||||
}).collect();
|
||||
let nodes_to_look_up = document
|
||||
.dataset()
|
||||
.quads
|
||||
.iter()
|
||||
.flat_map(|(_, quad)| {
|
||||
let mut nodes = vec![quad.predicate.clone()];
|
||||
if let Some(object) = term_to_named_node(&quad.object) {
|
||||
nodes.push(object.clone());
|
||||
}
|
||||
nodes
|
||||
})
|
||||
.collect();
|
||||
|
||||
let add_row_tasks = document.dataset().quads.iter().map(|(key, quad)| {
|
||||
let datatype_state = combo_box::State::new(self.abbreviated_datatypes.clone());
|
||||
@@ -374,9 +415,10 @@ impl Publisher {
|
||||
task = window_task.then(|_| operation::focus("query"));
|
||||
}
|
||||
|
||||
if let Some(selected_entity_class) = selected_entity_class &&
|
||||
let Some(class) = Class::try_from_named_node(selected_entity_class) {
|
||||
task = task.chain(Task::done(Message::UpdateQueryClass(class)))
|
||||
if let Some(selected_entity_class) = selected_entity_class
|
||||
&& let Some(class) = Class::try_from_named_node(selected_entity_class)
|
||||
{
|
||||
task = task.chain(Task::done(Message::UpdateQueryClass(class)))
|
||||
}
|
||||
}
|
||||
Message::UpdateQueryClass(type_) => {
|
||||
@@ -387,7 +429,8 @@ impl Publisher {
|
||||
}
|
||||
Message::QueryUpdated(new_query) => {
|
||||
if let Some(search_state) = &mut self.search_state {
|
||||
let category_id = search_state.entity_class_selection
|
||||
let category_id = search_state
|
||||
.entity_class_selection
|
||||
.as_ref()
|
||||
.map(|selection| selection.to_owned() as u64);
|
||||
|
||||
@@ -407,7 +450,9 @@ impl Publisher {
|
||||
}
|
||||
Message::SearchResultClicked(node) => {
|
||||
if let Some(search_state) = &self.search_state {
|
||||
task = task.chain(Task::done(Message::LookupResourceDescriptions(HashSet::from_iter([node.clone()]))));
|
||||
task = task.chain(Task::done(Message::LookupResourceDescriptions(
|
||||
HashSet::from_iter([node.clone()]),
|
||||
)));
|
||||
|
||||
match search_state.action {
|
||||
SearchResultClickAction::URLInput => {
|
||||
@@ -497,15 +542,18 @@ impl Publisher {
|
||||
Message::SaveGraph(overwrite) => {
|
||||
let client = self.http_client.clone();
|
||||
let read_only_entities = self.read_only_entities.clone();
|
||||
let options = SerializationOptions::from_format(RdfFormat::Turtle)
|
||||
.with_filter(move |triple| {
|
||||
if triple.predicate == vocab::rdf::TYPE &&
|
||||
let TermRef::NamedNode(node) = triple.object {
|
||||
let options = SerializationOptions::from_format(RdfFormat::Turtle).with_filter(
|
||||
move |triple| {
|
||||
if triple.predicate == vocab::rdf::TYPE
|
||||
&& let TermRef::NamedNode(node) = triple.object
|
||||
{
|
||||
!read_only_entities.contains(&ReadOnlyEntity::Class(node.into_owned()))
|
||||
} else {
|
||||
!read_only_entities.contains(&ReadOnlyEntity::Property(triple.predicate.into_owned()))
|
||||
!read_only_entities
|
||||
.contains(&ReadOnlyEntity::Property(triple.predicate.into_owned()))
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
let request = self
|
||||
.document
|
||||
.to_update(options)
|
||||
@@ -531,6 +579,58 @@ impl Publisher {
|
||||
task = Task::done(Message::SaveGraph(true))
|
||||
.chain(Task::done(Message::HideOverwriteConfirmationModal));
|
||||
}
|
||||
Message::DownloadResource => {
|
||||
let suggested_file_name = self.document.origin_file_name().unwrap_or_default();
|
||||
|
||||
let destination_selector =
|
||||
rfd::AsyncFileDialog::new().set_file_name(suggested_file_name);
|
||||
|
||||
task = Task::perform(destination_selector.save_file(), |result| {
|
||||
Message::DownloadLocationSelected(result.map(|fh| fh.path().to_owned()))
|
||||
});
|
||||
}
|
||||
Message::DownloadLocationSelected(None) => {
|
||||
debug!("canceled");
|
||||
}
|
||||
Message::DownloadLocationSelected(Some(path)) => {
|
||||
if let Ok(url) = Url::parse(&self.url_input) {
|
||||
let client = self.http_client.clone();
|
||||
task = Task::sip(
|
||||
sipper(async move |mut sender| {
|
||||
let request = ResourceRequestBuilder::with_client_and_url(client, url)
|
||||
.follow_described_by(false)
|
||||
.build();
|
||||
let resource = request.send().await?;
|
||||
|
||||
let mut handle = tokio::fs::File::create(path).await?;
|
||||
let mut progress = 0usize;
|
||||
let total_size = resource.size();
|
||||
|
||||
let mut stream = resource.into_stream();
|
||||
while let Some(chunk_result) = stream.next().await {
|
||||
let chunk = chunk_result?;
|
||||
handle.write_all(&chunk).await?;
|
||||
progress += chunk.len();
|
||||
sender.send((progress, total_size)).await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}),
|
||||
Message::DownloadProgress,
|
||||
|result: crate::error::Result<()>| {
|
||||
if let Err(err) = result {
|
||||
Message::ShowError(err.to_string())
|
||||
} else {
|
||||
Message::None
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Message::DownloadProgress((bytes_written, Some(bytes_total))) => {
|
||||
let percentage = bytes_written as f32 / bytes_total as f32;
|
||||
debug!(bytes_written, bytes_total, percentage);
|
||||
}
|
||||
Message::ShowNewDocumentButtons => {
|
||||
self.show_new_document_buttons = true;
|
||||
}
|
||||
@@ -549,8 +649,9 @@ impl Publisher {
|
||||
let quad = document.quad_from_triple(triple.into_owned());
|
||||
document.dataset_mut().quads.insert(quad);
|
||||
|
||||
if triple.predicate == vocab::rdf::TYPE &&
|
||||
let TermRef::NamedNode(node) = triple.object {
|
||||
if triple.predicate == vocab::rdf::TYPE
|
||||
&& let TermRef::NamedNode(node) = triple.object
|
||||
{
|
||||
nodes_to_look_up.insert(node.into_owned());
|
||||
} else {
|
||||
nodes_to_look_up.insert(triple.predicate.into_owned());
|
||||
@@ -581,7 +682,9 @@ impl Publisher {
|
||||
Url::parse(node.as_str()).ok()
|
||||
} else if let Term::Literal(literal) = &quad.object {
|
||||
Url::parse(literal.value()).ok()
|
||||
} else { None };
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(url) = url {
|
||||
task = Task::done(Message::URLInputChanged(url.to_string()))
|
||||
@@ -601,7 +704,8 @@ impl Publisher {
|
||||
}
|
||||
Message::RunInference => {
|
||||
if let Some(ontology) = &mut self.ontology {
|
||||
let graph = self.document
|
||||
let graph = self
|
||||
.document
|
||||
.dataset()
|
||||
.quads
|
||||
.iter()
|
||||
@@ -613,14 +717,12 @@ impl Publisher {
|
||||
}
|
||||
Message::SetInferredTriples(graph) => {
|
||||
if let Some(ontology) = &mut self.ontology {
|
||||
let predicates = graph.iter()
|
||||
.map(|triple| triple.predicate.into_owned());
|
||||
let predicates = graph.iter().map(|triple| triple.predicate.into_owned());
|
||||
|
||||
let objects = graph.iter()
|
||||
.filter_map(|triple| match triple.object {
|
||||
TermRef::NamedNode(node) => Some(node.into_owned()),
|
||||
_ => None,
|
||||
});
|
||||
let objects = graph.iter().filter_map(|triple| match triple.object {
|
||||
TermRef::NamedNode(node) => Some(node.into_owned()),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
let mut nodes_to_look_up: HashSet<NamedNode> = HashSet::from_iter(predicates);
|
||||
nodes_to_look_up.extend(objects);
|
||||
@@ -664,11 +766,17 @@ impl Publisher {
|
||||
}
|
||||
|
||||
pub(crate) fn title(&self, window: window::Id) -> String {
|
||||
if let Some(inference_window_id) = self.inference_window_id && window == inference_window_id {
|
||||
if let Some(inference_window_id) = self.inference_window_id
|
||||
&& window == inference_window_id
|
||||
{
|
||||
"Graph of Liberty Publisher - Inferences".to_string()
|
||||
} else if let Some(search_state) = &self.search_state && window == search_state.window_id {
|
||||
} 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 {
|
||||
} else if let Some(read_only_window_id) = self.read_only_window_id
|
||||
&& window == read_only_window_id
|
||||
{
|
||||
"Graph of Liberty Publisher - Read Only Triples".to_string()
|
||||
} else {
|
||||
"Graph of Liberty Publisher".to_string()
|
||||
@@ -682,43 +790,64 @@ 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() });
|
||||
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()))
|
||||
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 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());
|
||||
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");
|
||||
if let Some(label) = label {
|
||||
format!("{curie} ({label})")
|
||||
} else {
|
||||
curie
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
|
||||
text(textual_classes)
|
||||
});
|
||||
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); })
|
||||
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]));
|
||||
}
|
||||
}
|
||||
@@ -726,14 +855,24 @@ impl Publisher {
|
||||
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() });
|
||||
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()))
|
||||
Some(
|
||||
self.curie_helper
|
||||
.abbreviate(Some(self.document.origin()), subject.as_str())
|
||||
.unwrap_or_else(|| subject.as_str().to_string()),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -742,11 +881,13 @@ impl Publisher {
|
||||
});
|
||||
|
||||
let predicate_column = table::column(bold("Predicate"), |triple: TripleRef| {
|
||||
let curie = self.curie_helper
|
||||
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
|
||||
let label = self
|
||||
.resource_descriptions
|
||||
.get(&triple.predicate.into_owned())
|
||||
.and_then(|description| description.label.clone());
|
||||
|
||||
@@ -757,14 +898,16 @@ impl Publisher {
|
||||
}
|
||||
});
|
||||
|
||||
let object_column = table::column(bold("Object"), |triple: TripleRef| {
|
||||
match triple.object {
|
||||
let object_column =
|
||||
table::column(bold("Object"), |triple: TripleRef| match triple.object {
|
||||
TermRef::NamedNode(node) => {
|
||||
let curie = self.curie_helper
|
||||
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
|
||||
let label = self
|
||||
.resource_descriptions
|
||||
.get(&node.into_owned())
|
||||
.and_then(|description| description.label.clone());
|
||||
|
||||
@@ -775,8 +918,7 @@ impl Publisher {
|
||||
}
|
||||
}
|
||||
_ => text(triple.object.to_string()),
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
table([subject_column, predicate_column, object_column], triples).into()
|
||||
}
|
||||
@@ -790,45 +932,55 @@ impl Publisher {
|
||||
search_state.entity_class_selection.as_ref(),
|
||||
Class::ALL,
|
||||
ToString::to_string,
|
||||
).on_select(Message::UpdateQueryClass);
|
||||
)
|
||||
.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 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();
|
||||
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)
|
||||
})];
|
||||
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
|
||||
let fields = search_state
|
||||
.entity_class_selection
|
||||
.as_ref()
|
||||
.map(|class| {
|
||||
class.fields(language::ENGLISH_TAG.clone())
|
||||
}).unwrap_or_default();
|
||||
.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();
|
||||
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();
|
||||
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)),
|
||||
button(text(value).wrapping(Wrapping::Word))
|
||||
.on_press(Message::SearchResultClicked(NamedNode::new_unchecked(iri)))
|
||||
.style(button::text)
|
||||
},
|
||||
)
|
||||
.width(Length::Fixed(256.0)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -842,24 +994,26 @@ impl Publisher {
|
||||
}
|
||||
|
||||
fn view_inference_window(&self) -> Element<'_, Message> {
|
||||
let classes = self.inferred_triples.triples_for_predicate(vocab::rdf::TYPE);
|
||||
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()
|
||||
]
|
||||
.into()
|
||||
}
|
||||
|
||||
fn view_read_only_window(&self) -> Element<'_, Message> {
|
||||
let read_only_triples = self.document
|
||||
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()
|
||||
column![scrollable(self.view_triple_table(read_only_triples)),].into()
|
||||
}
|
||||
|
||||
fn view_row<'a>(
|
||||
@@ -910,14 +1064,16 @@ impl Publisher {
|
||||
.on_shift_click(Message::NavigateToPredicate(key))
|
||||
};
|
||||
|
||||
let predicate_label = self.resource_descriptions
|
||||
let predicate_label = self
|
||||
.resource_descriptions
|
||||
.get(&quad.predicate)
|
||||
.and_then(|description| description.label.clone())
|
||||
.map(|label| container(text(label)));
|
||||
|
||||
let term = TermHelper::new(&quad.object);
|
||||
|
||||
let value_label = term.value_as_named_node()
|
||||
let value_label = term
|
||||
.value_as_named_node()
|
||||
.and_then(|node| self.resource_descriptions.get(&node.into_owned()))
|
||||
.and_then(|description| description.label.clone())
|
||||
.map(|label| container(text(label)));
|
||||
@@ -938,28 +1094,29 @@ impl Publisher {
|
||||
/* If the object is not a valid URL (either as a literal string or as a named node), then
|
||||
* use a normal text input instead of an iri_input.
|
||||
*/
|
||||
let object_input: Element<Message> = if term.is_named_node() || Url::parse(term.value()).is_ok() {
|
||||
let base = iri_input(&self.curie_helper, "Object", Some(base), value.as_str())
|
||||
.on_shift_click(Message::NavigateToObject(key));
|
||||
if state.read_only {
|
||||
base.into()
|
||||
let object_input: Element<Message> =
|
||||
if term.is_named_node() || Url::parse(term.value()).is_ok() {
|
||||
let base = iri_input(&self.curie_helper, "Object", Some(base), value.as_str())
|
||||
.on_shift_click(Message::NavigateToObject(key));
|
||||
if state.read_only {
|
||||
base.into()
|
||||
} else {
|
||||
base.align_x(value_alignment)
|
||||
.on_input(move |value| Message::ObjectUpdated(key, value))
|
||||
.on_control_click(Message::OpenQueryWindow(
|
||||
SearchResultClickAction::Object(key),
|
||||
))
|
||||
.into()
|
||||
}
|
||||
} else {
|
||||
base.align_x(value_alignment)
|
||||
.on_input(move |value| Message::ObjectUpdated(key, value))
|
||||
.on_control_click(Message::OpenQueryWindow(SearchResultClickAction::Object(
|
||||
key,
|
||||
)))
|
||||
.into()
|
||||
}
|
||||
} else {
|
||||
let base = text_input("Object", value.clone());
|
||||
if state.read_only {
|
||||
base.into()
|
||||
} else {
|
||||
base.on_input(move |value| Message::ObjectUpdated(key, value))
|
||||
.into()
|
||||
}
|
||||
};
|
||||
let base = text_input("Object", value.clone());
|
||||
if state.read_only {
|
||||
base.into()
|
||||
} else {
|
||||
base.on_input(move |value| Message::ObjectUpdated(key, value))
|
||||
.into()
|
||||
}
|
||||
};
|
||||
|
||||
let selected_datatype = term
|
||||
.datatype()
|
||||
@@ -1032,16 +1189,21 @@ impl Publisher {
|
||||
}
|
||||
|
||||
pub(crate) fn view(&self, window: window::Id) -> Element<'_, Message> {
|
||||
if let Some(search_state) = &self.search_state &&
|
||||
search_state.window_id == window {
|
||||
if let Some(search_state) = &self.search_state
|
||||
&& search_state.window_id == window
|
||||
{
|
||||
return self.view_search_window(search_state);
|
||||
}
|
||||
|
||||
if let Some(inference_window_id) = self.inference_window_id && window == inference_window_id {
|
||||
if let Some(inference_window_id) = self.inference_window_id
|
||||
&& window == inference_window_id
|
||||
{
|
||||
return self.view_inference_window();
|
||||
}
|
||||
|
||||
if let Some(read_only_window_id) = self.read_only_window_id && window == read_only_window_id {
|
||||
if let Some(read_only_window_id) = self.read_only_window_id
|
||||
&& window == read_only_window_id
|
||||
{
|
||||
return self.view_read_only_window();
|
||||
}
|
||||
|
||||
@@ -1060,8 +1222,8 @@ impl Publisher {
|
||||
!self.url_input_valid,
|
||||
));
|
||||
|
||||
let mut rows: Vec<Element<Message>> = vec![];
|
||||
rows = self.document
|
||||
let rows: Vec<Element<Message>> = self
|
||||
.document
|
||||
.dataset()
|
||||
.iter_both()
|
||||
.filter(|(_, _, state)| !state.read_only)
|
||||
@@ -1069,24 +1231,31 @@ impl Publisher {
|
||||
.collect();
|
||||
|
||||
let body: Element<Message> = if self.show_new_document_buttons {
|
||||
let buttons = Class::CREATABLE.iter()
|
||||
.map(|class| {
|
||||
let url = if self.resource_not_found {
|
||||
Url::parse(&self.url_input).ok()
|
||||
} else { None };
|
||||
let buttons = Class::CREATABLE.iter().map(|class| {
|
||||
let url = if self.resource_not_found {
|
||||
Url::parse(&self.url_input).ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
button(text(class.to_string()))
|
||||
.on_press(Message::NewDocument(class.clone(), url))
|
||||
.into()
|
||||
});
|
||||
button(text(class.to_string()))
|
||||
.on_press(Message::NewDocument(class.clone(), url))
|
||||
.into()
|
||||
});
|
||||
|
||||
column![
|
||||
grid(buttons).height(Sizing::EvenlyDistribute(Length::Shrink))
|
||||
].into()
|
||||
column![grid(buttons).height(Sizing::EvenlyDistribute(Length::Shrink))].into()
|
||||
} else {
|
||||
column(rows).into()
|
||||
};
|
||||
|
||||
let download_button_base = button("\u{2b73}");
|
||||
let download_button =
|
||||
if self.document.origin_type() == Some(&ldp::ResourceType::NonRdfSource) {
|
||||
download_button_base.on_press(Message::DownloadResource)
|
||||
} else {
|
||||
download_button_base
|
||||
};
|
||||
|
||||
let save_button_base = button(text("\u{1f4be}"));
|
||||
let save_button = if self.modified {
|
||||
save_button_base.on_press(Message::SaveGraph(false))
|
||||
@@ -1103,6 +1272,7 @@ impl Publisher {
|
||||
back_button,
|
||||
forward_button,
|
||||
address_input,
|
||||
download_button,
|
||||
save_button,
|
||||
],
|
||||
scrollable(body),
|
||||
@@ -1157,4 +1327,4 @@ where
|
||||
)
|
||||
]
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ pub(crate) struct SearchArgs {
|
||||
pub(crate) discriminant: Option<u64>,
|
||||
|
||||
#[arg(short, long, value_name = "LIMIT")]
|
||||
pub(crate) limit: Option<usize>,
|
||||
pub(crate) limit: Option<usize>,
|
||||
|
||||
#[arg(value_name = "QUERY")]
|
||||
pub(crate) query: String,
|
||||
|
||||
@@ -42,10 +42,16 @@ pub(crate) enum Error {
|
||||
|
||||
#[error(transparent)]
|
||||
TonicTransport(#[from] tonic::transport::Error),
|
||||
|
||||
|
||||
#[error(transparent)]
|
||||
TonicStatus(#[from] tonic::Status),
|
||||
|
||||
|
||||
#[error(transparent)]
|
||||
Tantivy(#[from] gl_search::tantivy::TantivyError),
|
||||
}
|
||||
|
||||
#[error(transparent)]
|
||||
Reqwest(#[from] ldp::reqwest::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
Ldp(#[from] ldp::Error),
|
||||
}
|
||||
|
||||
+57
-30
@@ -3,32 +3,32 @@ mod args;
|
||||
mod error;
|
||||
mod navigator;
|
||||
mod rdf;
|
||||
mod tasks;
|
||||
mod theme;
|
||||
mod widget;
|
||||
mod tasks;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use crate::app::Publisher;
|
||||
use crate::args::{AppArgs, Command};
|
||||
use clap::Parser;
|
||||
use gl_graph::indexer::Indexer;
|
||||
use gl_graph::ontology::{OntologyBuilder, ResourceSelector};
|
||||
use gl_graph::{CurieHelper, language, vocab};
|
||||
use gl_inference::proto::OntologyQueryRequest;
|
||||
use gl_inference::proto::ontology_client::OntologyClient;
|
||||
use gl_search::{Schema, SearchIndex};
|
||||
use iced::futures::StreamExt;
|
||||
use ldp::Traverse;
|
||||
use ldp::middleware::BasicAuthMiddleware;
|
||||
use ldp::reqwest::Client;
|
||||
use ldp::reqwest_middleware::ClientBuilder;
|
||||
use ldp::traverse::Traverse;
|
||||
use oxigraph::model::{Graph, TripleRef};
|
||||
use tracing::{debug_span, error, field, Instrument};
|
||||
use std::collections::HashMap;
|
||||
use tracing::{Instrument, debug_span, error, field};
|
||||
use tracing_subscriber::fmt::format::FmtSpan;
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
use tracing_subscriber::{EnvFilter, fmt};
|
||||
use url::Url;
|
||||
use gl_graph::indexer::Indexer;
|
||||
use gl_graph::{language, CurieHelper, vocab};
|
||||
use gl_graph::ontology::{OntologyBuilder, ResourceSelector};
|
||||
use gl_inference::proto::ontology_client::OntologyClient;
|
||||
use gl_inference::proto::OntologyQueryRequest;
|
||||
|
||||
fn main() -> color_eyre::Result<()> {
|
||||
let appender = tracing_appender::rolling::never("/tmp", "publisher-log");
|
||||
@@ -47,16 +47,24 @@ fn main() -> color_eyre::Result<()> {
|
||||
Some(Command::Query(args)) => {
|
||||
let raw_query = if let Some(query) = &args.query_path {
|
||||
Some(String::from_utf8(std::fs::read(query)?)?)
|
||||
} else { None };
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let graph = if let Some(dataset_path) = &args.dataset_path {
|
||||
Some(String::from_utf8(std::fs::read(dataset_path)?)?)
|
||||
} else { None };
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let request = OntologyQueryRequest {
|
||||
sparql_query: raw_query,
|
||||
turtle: graph,
|
||||
prefixes: HashMap::from_iter(gl_graph::PREFIXES.iter().map( | (name, iri) | (name.clone(), iri.clone()))),
|
||||
prefixes: HashMap::from_iter(
|
||||
gl_graph::PREFIXES
|
||||
.iter()
|
||||
.map(|(name, iri)| (name.clone(), iri.clone())),
|
||||
),
|
||||
base: args.base.clone(),
|
||||
inferences_only: args.inferences_only,
|
||||
};
|
||||
@@ -83,7 +91,9 @@ fn main() -> color_eyre::Result<()> {
|
||||
.expect("Failed to build search index");
|
||||
|
||||
let limit = args.limit.unwrap_or(5);
|
||||
for document in index.query(args.discriminant, &args.query, Schema::all_fields(), limit)? {
|
||||
for document in
|
||||
index.query(args.discriminant, &args.query, Schema::all_fields(), limit)?
|
||||
{
|
||||
println!("{}", gl_search::to_json(document));
|
||||
}
|
||||
}
|
||||
@@ -108,14 +118,22 @@ fn main() -> color_eyre::Result<()> {
|
||||
let curie_helper = CurieHelper::new(gl_graph::PREFIXES.clone());
|
||||
let indexer = Indexer::new(language::ENGLISH_OR_UNTAGGED.clone(), &curie_helper);
|
||||
|
||||
let mut client = OntologyBuilder::from_string("http://[::1]:3000", language::ENGLISH_OR_UNTAGGED.clone())?
|
||||
.connect()
|
||||
let mut client = OntologyBuilder::from_string(
|
||||
"http://[::1]:3000",
|
||||
language::ENGLISH_OR_UNTAGGED.clone(),
|
||||
)?
|
||||
.connect()
|
||||
.await?;
|
||||
let resource_descriptions = client
|
||||
.resource_descriptions(
|
||||
ResourceSelector::Classes,
|
||||
[
|
||||
vocab::rdf::PROPERTY.into_owned(),
|
||||
vocab::rdfs::CLASS.into_owned(),
|
||||
vocab::skos::CONCEPT.into_owned(),
|
||||
],
|
||||
)
|
||||
.await?;
|
||||
let resource_descriptions = client.resource_descriptions(ResourceSelector::Classes, [
|
||||
vocab::rdf::PROPERTY.into_owned(),
|
||||
vocab::rdfs::CLASS.into_owned(),
|
||||
vocab::skos::CONCEPT.into_owned(),
|
||||
]).await?;
|
||||
let documents = indexer.ontology(resource_descriptions);
|
||||
debug_span!("Index Ontology", documents = field::Empty).in_scope(|| {
|
||||
for document in documents {
|
||||
@@ -140,24 +158,31 @@ fn main() -> color_eyre::Result<()> {
|
||||
while let Some(result) = traversal.next().await {
|
||||
match result {
|
||||
Ok(rdf_source) => {
|
||||
let triples = rdf_source.dataset()
|
||||
.iter()
|
||||
.map(TripleRef::from);
|
||||
let triples = rdf_source.dataset().iter().map(TripleRef::from);
|
||||
graph.extend(triples);
|
||||
rdf_source_count += 1;
|
||||
},
|
||||
}
|
||||
Err(err) => error!(?err),
|
||||
}
|
||||
}
|
||||
|
||||
let span = debug_span!("Index Repository", rdf_sources = rdf_source_count, triples = field::Empty, documents = field::Empty);
|
||||
let span = debug_span!(
|
||||
"Index Repository",
|
||||
rdf_sources = rdf_source_count,
|
||||
triples = field::Empty,
|
||||
documents = field::Empty
|
||||
);
|
||||
let triple_count = async {
|
||||
let curie_helper = CurieHelper::new(gl_graph::PREFIXES.clone());
|
||||
let indexer = Indexer::new(language::ENGLISH_OR_UNTAGGED.clone(), &curie_helper);
|
||||
let indexer =
|
||||
Indexer::new(language::ENGLISH_OR_UNTAGGED.clone(), &curie_helper);
|
||||
|
||||
let mut client = OntologyBuilder::from_string("http://[::1]:3000", language::ENGLISH_OR_UNTAGGED.clone())?
|
||||
.connect()
|
||||
.await?;
|
||||
let mut client = OntologyBuilder::from_string(
|
||||
"http://[::1]:3000",
|
||||
language::ENGLISH_OR_UNTAGGED.clone(),
|
||||
)?
|
||||
.connect()
|
||||
.await?;
|
||||
|
||||
let graph_with_inferences = client.run_inference(&graph).await?;
|
||||
for document in indexer.graph(&graph_with_inferences) {
|
||||
@@ -165,7 +190,9 @@ fn main() -> color_eyre::Result<()> {
|
||||
}
|
||||
|
||||
Ok::<usize, crate::error::Error>(graph_with_inferences.len())
|
||||
}.instrument(span.clone()).await?;
|
||||
}
|
||||
.instrument(span.clone())
|
||||
.await?;
|
||||
span.record("triples", triple_count);
|
||||
|
||||
Ok::<_, crate::error::Error>(writer)
|
||||
|
||||
@@ -33,4 +33,4 @@ impl<T> Navigator<T> {
|
||||
}
|
||||
self.current()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+66
-38
@@ -1,57 +1,73 @@
|
||||
use iced::Task;
|
||||
use ldp::reqwest_middleware::ClientWithMiddleware;
|
||||
use ldp::ResourceRequestBuilder;
|
||||
use oxigraph::io::RdfFormat;
|
||||
use oxigraph::model::{Dataset, Graph, NamedNode, NamedNodeRef, NamedOrBlankNodeRef, TermRef};
|
||||
use url::Url;
|
||||
use crate::app::Message;
|
||||
use gl_graph::class::Class;
|
||||
use gl_graph::ontology::{ConnectedOntology, OntologyBuilder, ResourceSelector};
|
||||
use gl_graph::vocab;
|
||||
use crate::app::Message;
|
||||
use iced::Task;
|
||||
use ldp::ResourceRequestBuilder;
|
||||
use ldp::reqwest_middleware::ClientWithMiddleware;
|
||||
use oxigraph::io::RdfFormat;
|
||||
use oxigraph::model::{Dataset, Graph, NamedNode, NamedNodeRef, NamedOrBlankNodeRef, TermRef};
|
||||
use url::Url;
|
||||
|
||||
pub(crate) fn connect_to_ontology_service(builder: OntologyBuilder) -> Task<Message> {
|
||||
Task::perform(builder.connect(), |result| {
|
||||
match result {
|
||||
Ok(ontology) => Message::ConnectedToOntologyService(ontology),
|
||||
Err(err) => Message::ShowError(err.to_string())
|
||||
}
|
||||
Task::perform(builder.connect(), |result| match result {
|
||||
Ok(ontology) => Message::ConnectedToOntologyService(ontology),
|
||||
Err(err) => Message::ShowError(err.to_string()),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn lookup_resource_descriptions(mut ontology: ConnectedOntology, subjects: impl IntoIterator<Item = NamedNode> + Send + 'static) -> Task<Message> {
|
||||
Task::perform(async move { ontology.resource_descriptions(ResourceSelector::Subjects, subjects).await }, |result| {
|
||||
match result {
|
||||
pub(crate) fn lookup_resource_descriptions(
|
||||
mut ontology: ConnectedOntology,
|
||||
subjects: impl IntoIterator<Item = NamedNode> + Send + 'static,
|
||||
) -> Task<Message> {
|
||||
Task::perform(
|
||||
async move {
|
||||
ontology
|
||||
.resource_descriptions(ResourceSelector::Subjects, subjects)
|
||||
.await
|
||||
},
|
||||
|result| match result {
|
||||
Ok(descriptions) => Message::CacheResourceDescriptions(descriptions),
|
||||
Err(err) => Message::ShowError(err.to_string()),
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn list_datatypes(mut ontology: ConnectedOntology) -> Task<Message> {
|
||||
Task::perform(async move { ontology.resource_descriptions(ResourceSelector::Classes, vec![vocab::rdfs::DATATYPE.into_owned()]).await }, |result| {
|
||||
match result {
|
||||
Task::perform(
|
||||
async move {
|
||||
ontology
|
||||
.resource_descriptions(
|
||||
ResourceSelector::Classes,
|
||||
vec![vocab::rdfs::DATATYPE.into_owned()],
|
||||
)
|
||||
.await
|
||||
},
|
||||
|result| match result {
|
||||
Ok(datatypes) => Message::CacheDatatypes(datatypes),
|
||||
Err(err) => Message::ShowError(err.to_string()),
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn list_read_only_entities(mut ontology: ConnectedOntology) -> Task<Message> {
|
||||
Task::perform(async move { ontology.list_read_only().await }, |result| {
|
||||
match result {
|
||||
Task::perform(
|
||||
async move { ontology.list_read_only().await },
|
||||
|result| match result {
|
||||
Ok(entities) => Message::CacheReadOnlyEntities(entities),
|
||||
Err(err) => Message::ShowError(err.to_string()),
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn run_inference(mut ontology: ConnectedOntology, graph: Graph) -> Task<Message> {
|
||||
Task::perform(async move { ontology.run_inference(&graph).await }, |result| {
|
||||
match result {
|
||||
Task::perform(
|
||||
async move { ontology.run_inference(&graph).await },
|
||||
|result| match result {
|
||||
Ok(inferences) => Message::SetInferredTriples(inferences),
|
||||
Err(err) => Message::ShowError(err.to_string()),
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn navigate_to_next_url(client: ClientWithMiddleware, class: Class) -> Task<Message> {
|
||||
@@ -66,20 +82,32 @@ pub(crate) fn navigate_to_next_url(client: ClientWithMiddleware, class: Class) -
|
||||
Ok(resource) => match resource.into_rdf_source::<Dataset>().await {
|
||||
Ok(document) => {
|
||||
let node = NamedNodeRef::new_unchecked(lookup_url.as_str());
|
||||
let result = document.dataset()
|
||||
.quads_for_pattern(Some(NamedOrBlankNodeRef::NamedNode(node)), Some(vocab::glo::NEXT_URL), None, None)
|
||||
let result = document
|
||||
.dataset()
|
||||
.quads_for_pattern(
|
||||
Some(NamedOrBlankNodeRef::NamedNode(node)),
|
||||
Some(vocab::glo::NEXT_URL),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.next();
|
||||
if let Some(next_url_quad) = result {
|
||||
if let TermRef::NamedNode(next_url) = next_url_quad.object {
|
||||
let url = Url::parse(next_url.as_str()).unwrap();
|
||||
Message::NewDocument(class, Some(url))
|
||||
} else { Message::None }
|
||||
} else { Message::None }
|
||||
} else {
|
||||
Message::None
|
||||
}
|
||||
} else {
|
||||
Message::None
|
||||
}
|
||||
}
|
||||
_ => Message::None
|
||||
}
|
||||
_ => Message::None
|
||||
_ => Message::None,
|
||||
},
|
||||
_ => Message::None,
|
||||
}
|
||||
})
|
||||
} else { Task::none() }
|
||||
}
|
||||
} else {
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,7 +214,8 @@ where
|
||||
state.shift = modifiers.shift();
|
||||
}
|
||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
||||
if cursor.is_over(layout.bounds()) => {
|
||||
if cursor.is_over(layout.bounds()) =>
|
||||
{
|
||||
if state.control
|
||||
&& let Some(on_control_click) = &self.on_control_click
|
||||
{
|
||||
@@ -244,8 +245,9 @@ where
|
||||
);
|
||||
|
||||
let clipboard = shell.clipboard_mut();
|
||||
if let Some(Content::Text(content)) = &clipboard.write &&
|
||||
let Some(expanded) = self.curie_helper.expand(self.base, content) {
|
||||
if let Some(Content::Text(content)) = &clipboard.write
|
||||
&& let Some(expanded) = self.curie_helper.expand(self.base, content)
|
||||
{
|
||||
clipboard.write = Some(Content::Text(expanded));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user