From 78243f53f521b2d06676fc8861c51fb3b68049a0e5a957a265ddc0b67ebb9181 Mon Sep 17 00:00:00 2001 From: Alex Wied <2+alex@noreply.code.graphofliberty.org> Date: Mon, 24 Aug 2026 21:44:23 -0400 Subject: [PATCH] . --- graph/src/class.rs | 10 +-- graph/src/language.rs | 8 +-- publish/src/app.rs | 160 +++++++++++++++++++++--------------------- 3 files changed, 85 insertions(+), 93 deletions(-) diff --git a/graph/src/class.rs b/graph/src/class.rs index 7cb5a3a..34b2ea8 100644 --- a/graph/src/class.rs +++ b/graph/src/class.rs @@ -73,42 +73,34 @@ impl Class { } } - pub fn fields(&self, language: LanguageTag<&str>) -> Vec<(gl_search::Field, String)> { + pub fn fields(&self, language: LanguageTag) -> Vec<(gl_search::Field, String)> { match self { Class::RdfProperty => vec![ - (Schema::curie_field(), String::from("CURIE")), (Schema::field("label", Some(language.primary_language())), String::from("Label")), (Schema::field("definition", Some(language.primary_language())), String::from("Definition")), ], Class::RdfsClass => vec![ - (Schema::curie_field(), String::from("CURIE")), (Schema::field("label", Some(language.primary_language())), String::from("Label")), (Schema::field("definition", Some(language.primary_language())), String::from("Definition")), ], Class::SkosConcept => vec![ - (Schema::curie_field(), String::from("CURIE")), (Schema::field("label", Some(language.primary_language())), String::from("Label")), (Schema::field("definition", Some(language.primary_language())), String::from("Definition")), ], Class::Work => vec![ - (Schema::curie_field(), String::from("CURIE")), (Schema::field("title", Some(language.primary_language())), String::from("Title")), ], Class::Person => vec![ - (Schema::curie_field(), String::from("CURIE")), (Schema::field("given_name", Some(language.primary_language())), String::from("Given Name")), (Schema::field("surname", Some(language.primary_language())), String::from("Surname")), ], Class::CorporateBody => vec![ - (Schema::curie_field(), String::from("CURIE")), (Schema::field("corporate_body", Some(language.primary_language())), String::from("Corporate Body")), ], Class::Expression => vec![ - (Schema::curie_field(), String::from("CURIE")), (Schema::field("title", Some(language.primary_language())), String::from("Title")), ], Class::Manifestation => vec![ - (Schema::curie_field(), String::from("CURIE")), (Schema::field("title", Some(language.primary_language())), String::from("Title")), ], } diff --git a/graph/src/language.rs b/graph/src/language.rs index 8b4e6a9..31933cd 100644 --- a/graph/src/language.rs +++ b/graph/src/language.rs @@ -6,12 +6,12 @@ use spargebra::algebra::{Expression, Function, GraphPattern}; use spargebra::term::{NamedNode, NamedNodePattern, TermPattern, TriplePattern, Variable}; use tracing::debug; -pub const ENGLISH_PRIMARY: &str = "en"; +pub static ENGLISH_TAG: LazyLock> = LazyLock::new(|| { + LanguageTag::parse("en".to_string()).unwrap() +}); pub static ENGLISH_OR_UNTAGGED: LazyLock = LazyLock::new(|| { - LanguageCondition::ExactMatchOrUntagged( - LanguageTag::parse(ENGLISH_PRIMARY.to_string()).unwrap(), - ) + LanguageCondition::ExactMatchOrUntagged(ENGLISH_TAG.clone()) }); #[derive(Clone, Debug)] diff --git a/publish/src/app.rs b/publish/src/app.rs index 78e7c45..a723b44 100644 --- a/publish/src/app.rs +++ b/publish/src/app.rs @@ -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>) -> 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> = 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::>() - }) - .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, - ) -> 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| { + let mut columns = vec![table::column(text("CURIE"), |document: &HashMap| { 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| { + 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 = /*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 = 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| {