This commit is contained in:
2026-08-05 11:21:25 -04:00
parent 921d56f2b3
commit 7ce5ed118d
18 changed files with 1144 additions and 213 deletions
+31 -111
View File
@@ -1,40 +1,34 @@
use std::collections::HashMap;
use crate::rdf::ontology::{IndexEntry, LabeledIri, Ontology};
use crate::navigator::Navigator;
use crate::rdf::curie::CurieHelper;
use crate::rdf::ontology::{LabeledIri, Ontology};
use crate::rdf::term_helper::{TermHelper, TermHelperMut};
use gl_search::{Schema, SearchDocument, SearchIndex, doc, Value};
use crate::rdf::vocab::rda;
use crate::widget::iri_input::iri_input;
use crate::widget::navigation_area::navigation_area;
use gl_search::language;
use gl_search::{IndexWriter, Schema, SearchDocument, SearchIndex, Value};
use http::StatusCode;
use iced::alignment::Horizontal;
use iced::keyboard::{Event, key};
use iced::widget::button::Style;
use iced::widget::grid::Sizing;
use iced::widget::text::Wrapping;
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, window};
use iced::widget::text::Wrapping;
use iced::{Background, Color, Element, Length, Subscription, Task, color, keyboard, window};
use ldp::middleware::BasicAuthMiddleware;
use ldp::model::{KeyedDataset, QuadKey};
use ldp::reqwest::{Client, Url};
use ldp::reqwest_middleware::{ClientBuilder, ClientWithMiddleware};
use ldp::traverse::Traverse;
use ldp::{RdfSource, RdfSourceUpdateResponse, ResourceRequestBuilder, SerializationOptions};
use oxigraph::io::RdfFormat;
use oxigraph::model::vocab::{rdf, rdfs};
use oxigraph::model::{BaseDirection, Dataset, NamedNode, NamedOrBlankNode, Quad, Term};
use tracing::{debug_span, error, field, trace};
use crate::navigator::Navigator;
use crate::rdf::curie::CurieHelper;
use crate::rdf::language;
use crate::rdf::vocab::rda;
use crate::widget::iri_input::iri_input;
use crate::widget::navigation_area::navigation_area;
use tracing::{debug_span, error, trace};
#[derive(Clone, Debug)]
pub(crate) enum Message {
None,
RebuildIndex,
Traverse,
AddRdfSource(RdfSource<Dataset>),
ConcludeTraversal,
IndexQueryResults(HashMap<NamedNode, IndexEntry>),
WindowClosed(window::Id),
URLInputChanged(String),
URLInputSubmitted,
@@ -71,6 +65,7 @@ pub(crate) enum Message {
NavigateForward,
ResetState,
SetReadOnly(bool),
Event(Event),
}
#[derive(Default, Debug, Clone)]
@@ -181,90 +176,6 @@ impl Publisher {
trace!(?message);
match message {
Message::RebuildIndex => {
let mut writer = self.index.writer().expect("Unable to obtain index writer");
let clear_index_task = Task::perform(tokio::task::spawn_blocking(move || {
let _span = debug_span!("Clear Index").entered();
writer.delete_all_documents().expect("Unable to clear index");
writer.commit().expect("Unable to commit index operation");
}), |_| Message::None);
let index_future = self.ontology
.query_for_indexable_triples(&*language::ENGLISH_OR_UNTAGGED, None);
let index_task = Task::perform(index_future, |task_result| {
match task_result {
Ok(results) => Message::IndexQueryResults(results),
Err(err) => Message::ShowError(err.to_string()),
}
});
task = clear_index_task.chain(Task::batch([
index_task,
Task::done(Message::Traverse),
]));
}
Message::Traverse => {
let client = self.http_client.clone();
let root = Url::parse(&self.url_input).expect("Invalid URL");
let stream = Traverse::new(client, root, None);
task = Task::run(stream, |result| match result {
Ok(rdf_source) => Message::AddRdfSource(rdf_source),
Err(err) => Message::ShowError(format!("Unable to fetch RDF Source: {err}")),
}).chain(Task::done(Message::ConcludeTraversal));
self.traversal = Some(self.ontology.to_dataset());
}
Message::AddRdfSource(rdf_source) => {
if let Some(traversal) = &mut self.traversal {
traversal.extend(rdf_source.dataset());
}
}
Message::ConcludeTraversal => {
if let Some(traversal) = self.traversal.take() {
let index_task = self.ontology
.query_for_indexable_triples(&*language::ENGLISH_OR_UNTAGGED, Some(traversal));
task = Task::perform(index_task, |task_result| {
match task_result {
Ok(results) => Message::IndexQueryResults(results),
Err(err) => Message::ShowError(err.to_string()),
}
});
}
}
Message::IndexQueryResults(results) => {
let mut writer = self.index.writer().expect("Unable to obtain index writer");
let curie_helper = self.curie_helper.clone();
task = Task::perform(tokio::task::spawn_blocking(move || {
let span = debug_span!("Index Query Results", documents = field::Empty).entered();
let mut counter = 0usize;
for (individual, entry) in results {
let mut document = doc!(
Schema::type_field() => entry.category_id,
Schema::iri_field() => individual.as_str(),
);
if let Some(curie) = curie_helper.abbreviate(None, individual.as_str()) {
document.add_text(Schema::curie_field(), curie);
}
for (key, value) in entry.fields {
document.add_text(Schema::field(&key, language::ENGLISH_PRIMARY), value.as_str());
}
writer.add_document(document).expect("Failed to add document to search index");
counter += 1;
}
span.record("documents", counter);
writer.commit()
}), |result| {
if let Err(err) = result {
Message::ShowError(err.to_string())
} else {
Message::None
}
});
}
Message::WindowClosed(id) => {
if self.window_id == id {
task = iced::exit();
@@ -355,9 +266,8 @@ impl Publisher {
.map(|datatype| self.curie_helper.abbreviate(None, datatype.as_str())
.unwrap_or(datatype.as_str().to_string()));
let datatype_state = combo_box::State::with_selection(
let datatype_state = combo_box::State::new(
self.abbreviated_datatypes.clone(),
datatype.as_ref(),
);
let state = RowState {
read_only,
@@ -425,7 +335,7 @@ impl Publisher {
let query = new_query.clone();
search_state.query = new_query;
let results = self.index.query(category_id, query.as_str(), Schema::all_fields(), 10)
let results = self.index.query(category_id, query.as_str(), Schema::all_fields(), 25)
.expect("Unable to complete search");
task = Task::done(Message::SetSearchResults(results));
};
@@ -616,6 +526,17 @@ impl Publisher {
Message::SetReadOnly(value) => {
self.show_read_only = value;
}
Message::Event(Event::KeyPressed {
key: keyboard::Key::Named(key::Named::Tab),
modifiers,
..
}) => {
if modifiers.shift() {
task = operation::focus_previous();
} else {
task = operation::focus_next();
}
}
_ => {}
}
task
@@ -628,6 +549,7 @@ impl Publisher {
pub(crate) fn subscription(&self) -> Subscription<Message> {
Subscription::batch([
window::close_events().map(Message::WindowClosed),
keyboard::listen().map(Message::Event),
])
}
@@ -697,7 +619,7 @@ impl Publisher {
// If the datatype is something, then it's not a named node, and we should use a plain
// text_input as opposed to an iri_input.
let object_input: Element<Message> = if term.datatype().is_some() {
let base = text_input("Object", value.as_str());
let base = text_input("Object", value.clone());
if self.ontology.is_read_only(triple.as_ref()) {
base.into()
} else {
@@ -734,9 +656,10 @@ impl Publisher {
}).into()
};
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(
text_input("Language", term.language().unwrap_or("en")).on_input(move |input| {
text_input("Language", language).on_input(move |input| {
let value = if input.is_empty() { None } else { Some(input) };
Message::LanguageUpdated(key, value)
}),
@@ -848,7 +771,7 @@ impl Publisher {
.and_then(|value| value.as_str())
.unwrap_or_default();
let value = document.get_first(Schema::field(&field_name, language::ENGLISH_PRIMARY))
let value = document.get_first(Schema::field(&field_name, Some(language::ENGLISH_PRIMARY)))
.and_then(|value| value.as_str())
.unwrap_or_default();
@@ -877,8 +800,6 @@ impl Publisher {
let add_row_button = button("Add row").on_press(Message::AddRow(None));
let reindex_ontology_button = button("Rebuild index").on_press(Message::RebuildIndex);
let address_input = iri_input(&self.curie_helper, "URL", None, &self.url_input)
.on_input(Message::URLInputChanged)
.on_submit(Message::URLInputSubmitted)
@@ -920,7 +841,6 @@ impl Publisher {
let content = navigation_area(column![
row![
reindex_ontology_button,
add_row_button,
back_button,
forward_button,