This commit is contained in:
2026-08-24 21:44:23 -04:00
parent 70fca07790
commit 78243f53f5
3 changed files with 85 additions and 93 deletions
+80 -80
View File
@@ -4,31 +4,29 @@ use crate::navigator::Navigator;
use crate::rdf::term_helper::{TermHelper, TermHelperMut};
use crate::widget::iri_input::iri_input;
use crate::widget::navigation_area::navigation_area;
use gl_graph::{language, CurieHelper};
use gl_graph::{language, CurieHelper, vocab};
use gl_search::{Schema, SearchIndex};
use http::StatusCode;
use iced::alignment::Horizontal;
use iced::keyboard::{Event, key};
use iced::widget::button::Style;
use iced::widget::{
button, center, column, combo_box, container, mouse_area, opaque, operation, pick_list,
row, scrollable, space, stack, table, text, text_input, toggler,
};
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::advanced::text::Wrapping;
use iced::widget::grid::Sizing;
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::vocab::{rdf, rdfs};
use oxigraph::model::{BaseDirection, Graph, NamedNode, NamedNodeRef, NamedOrBlankNode, NamedOrBlankNodeRef, Quad, Term, TermRef, Triple, TripleRef};
use tracing::{debug, error, trace};
use oxigraph::model::{BaseDirection, Graph, NamedNode, NamedOrBlankNode, NamedOrBlankNodeRef, Quad, Term, TermRef, Triple, TripleRef};
use tracing::{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)]
@@ -130,7 +128,7 @@ 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 == rdf::TYPE &&
if triple.predicate == vocab::rdf::TYPE &&
let TermRef::NamedNode(node) = triple.object {
self.read_only_entities.contains(&ReadOnlyEntity::Class(node.into_owned()))
} else {
@@ -204,9 +202,14 @@ impl Publisher {
}
Message::PopulateCaches => {
if let Some(ontology) = &mut self.ontology {
let classes = Class::ALL.iter().map(|class| {
class.to_named_node().into_owned()
});
task = Task::batch([
tasks::list_datatypes(ontology.clone()),
tasks::list_read_only_entities(ontology.clone()),
tasks::lookup_resource_descriptions(ontology.clone(), classes),
])
}
}
@@ -287,6 +290,14 @@ impl Publisher {
});
}
Message::LoadDocument(document) => {
let nodes_to_look_up = document.dataset().quads.iter().map(|(_, quad)| {
let mut nodes = vec![ quad.predicate.clone() ];
if let Some(object) = term_to_named_node(&quad.object) {
nodes.push(object.clone());
}
nodes
}).flatten().collect();
let add_row_tasks = document.dataset().quads.iter().map(|(key, quad)| {
let datatype_state = combo_box::State::new(self.abbreviated_datatypes.clone());
let state = RowState {
@@ -295,7 +306,8 @@ impl Publisher {
};
Task::done(Message::AddRow(Some((key, state))))
});
task = Task::batch(add_row_tasks)
task = Task::done(Message::LookupResourceDescriptions(nodes_to_look_up))
.chain(Task::batch(add_row_tasks))
.chain(Task::done(Message::RunInference))
.chain(Task::done(Message::ResetState));
@@ -336,11 +348,11 @@ impl Publisher {
}
Message::OpenQueryWindow(action) => {
let selected_entity_class = match action {
SearchResultClickAction::Predicate(_) => Some(rdf::PROPERTY),
SearchResultClickAction::Predicate(_) => Some(vocab::rdf::PROPERTY),
SearchResultClickAction::Object(key) => {
self.document.dataset().quads.get(key).and_then(|quad| {
if quad.predicate == rdf::TYPE {
Some(rdfs::CLASS)
if quad.predicate == vocab::rdf::TYPE {
Some(vocab::rdfs::CLASS)
} else {
None
}
@@ -486,11 +498,11 @@ impl Publisher {
let read_only_entities = self.read_only_entities.clone();
let options = SerializationOptions::from_format(RdfFormat::Turtle)
.with_filter(move |triple| {
if triple.predicate == rdf::TYPE &&
if triple.predicate == vocab::rdf::TYPE &&
let TermRef::NamedNode(node) = triple.object {
read_only_entities.contains(&ReadOnlyEntity::Class(node.into_owned()))
!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
@@ -526,28 +538,14 @@ impl Publisher {
}
Message::NewDocument(class) => {
let url = Url::parse(self.url_input.as_str()).expect("Invalid URL");
let subject = NamedNode::new_unchecked(url.as_str());
self.document = RdfSource::new(url);
let mut document: RdfSource<KeyedDataset<RowState>> = RdfSource::new(url);
let mut quad = document.new_quad();
quad.predicate = vocab::rdf::TYPE.into_owned();
quad.object = Term::NamedNode(class);
document.dataset_mut().quads.insert(quad);
/*let template_quads = self
.ontology
.template_triples(class.as_ref(), subject.as_ref())
.map(|triples| {
triples
.map(|triple| self.document.quad_from_triple(triple))
.collect::<Vec<_>>()
})
.unwrap_or_else(|| {
let mut new_quad = self.document.new_quad();
new_quad.object = Term::NamedNode(class);
vec![new_quad]
});
task = Task::done(Message::HideNewDocumentButtons);
let new_keys = self.document.dataset_mut().extend(template_quads.into_iter());
let messages = new_keys.map(|triple| Message::AddRow(Some(triple)));
task = task.chain(Task::batch(messages.map(Task::done)));*/
task = Task::done(Message::HideNewDocumentButtons)
.chain(Task::done(Message::LoadDocument(document)));
}
Message::ResetState => {
self.modified = false;
@@ -763,7 +761,7 @@ impl Publisher {
let language = term.language().unwrap_or("en").to_owned();
let language_input = match term.datatype() {
Some(rdf::LANG_STRING) | Some(rdf::DIR_LANG_STRING) => Some(container(
Some(vocab::rdf::LANG_STRING) | Some(vocab::rdf::DIR_LANG_STRING) => Some(container(
text_input("Language", language).on_input(move |input| {
let value = if input.is_empty() { None } else { Some(input) };
Message::LanguageUpdated(key, value)
@@ -811,28 +809,6 @@ impl Publisher {
.into()
}
fn view_new_entity_buttons(
&self,
entities: impl IntoIterator<Item = ResourceDescription>,
) -> Element<'_, Message> {
/*let buttons = entities.into_iter().map(|entity| {
let abbreviation = self
.curie_helper
.abbreviate(None, entity.label.as_str())
.unwrap_or_else(|| entity.as_str().to_string());
let label = format!("{} ({})", entity.label, abbreviation);
button(text(label))
.on_press(Message::NewDocument(entity.iri))
.into()
});
grid(buttons)
.height(Sizing::EvenlyDistribute(Length::Shrink))
.into()*/
text("to do").into()
}
pub(crate) fn view(&self, window: window::Id) -> Element<'_, Message> {
if let Some(search_state) = &self.search_state
&& search_state.window_id == window
@@ -847,31 +823,35 @@ impl Publisher {
ToString::to_string,
).on_select(|selection| Message::UpdateQueryClass(selection));
let columns = vec![table::column(text("CURIE"), |document: &HashMap<gl_search::Field, gl_search::OwnedValue>| {
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 = self.curie_helper
.abbreviate(None, iri)
.unwrap_or_else(|| iri.to_string());
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)
})];
/*for field in {
let field_name = field.name.clone();
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(field.label), move |document: &SearchDocument| {
let iri = document
.get_first(Schema::iri_field())
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_first(Schema::field(&field_name, Some(language::ENGLISH_PRIMARY)))
.get(&field)
.and_then(|value| value.as_str())
.unwrap_or_default();
@@ -881,7 +861,7 @@ impl Publisher {
})
.width(Length::Fixed(256.0)),
);
}*/
}
let results_table = if columns.is_empty() {
None
@@ -912,15 +892,35 @@ impl Publisher {
.map(|(key, quad, state)| self.view_row(key, quad, state))
.collect();
let body: Element<Message> = /*if self.show_new_document_buttons {
let subclasses = Vec::new(); // TODO self.ontology.subclasses_of(vocab::rda::ENTITY.into_owned());
let labeled_subclasses = subclasses
.iter()
.map(|iri| self.ontology.info(iri, &*language::ENGLISH_OR_UNTAGGED));
column![self.view_new_entity_buttons(labeled_subclasses)].into()
} else {*/
column(rows).into();
//};
let body: Element<Message> = if self.show_new_document_buttons {
let buttons = Class::ALL.iter()
.map(|class| {
let node = class.to_named_node().into_owned();
let abbreviation = self.curie_helper
.abbreviate(None, node.as_str())
.unwrap_or(node.to_string());
let description = self.resource_descriptions
.get(&node);
let button_text = if let Some(description) = description &&
let Some(label) = &description.label {
format!("{label} ({abbreviation})")
} else {
abbreviation
};
button(text(button_text))
.on_press(Message::NewDocument(node))
.into()
});
column![
grid(buttons).height(Sizing::EvenlyDistribute(Length::Shrink))
].into()
} else {
column(rows).into()
};
let inference_table = if self.show_inferred_triples {
let subject_column = table::column("Subject", |triple: TripleRef| {