From 2618fe0d725aec69ada7d36fc906bd09206d92a3981aa0e7ebf31677164a483a Mon Sep 17 00:00:00 2001 From: Alex Wied <2+alex@noreply.code.graphofliberty.org> Date: Fri, 24 Jul 2026 19:25:07 -0400 Subject: [PATCH] . --- Cargo.lock | 14 ++++++++--- publish/src/app.rs | 44 ++++++++++++++++++++++++--------- publish/src/rdf/curie.rs | 19 ++++++++++---- publish/src/rdf/ontology.rs | 2 ++ publish/src/widget/iri_input.rs | 15 ++++++----- 5 files changed, 68 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cf8440d..ffda1e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -328,6 +328,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b25655df2c3cdd83c5e5b293b88acd880332b2ddadd7c30ac43144fdc0033da9" + [[package]] name = "bindgen" version = "0.72.1" @@ -1622,7 +1628,7 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "futures-channel", "futures-util", @@ -2099,7 +2105,7 @@ name = "ldp" version = "0.0.0" dependencies = [ "async-trait", - "base64", + "base64 0.23.0", "bytes", "futures", "http", @@ -3468,7 +3474,7 @@ version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "futures-core", "futures-util", @@ -4104,7 +4110,7 @@ checksum = "edde6a10743fff00a4e1a8c9ef020bf5f3cbad301b7d2d39f2b07f123c4eac07" dependencies = [ "aho-corasick", "arc-swap", - "base64", + "base64 0.22.1", "bitpacking", "bon", "byteorder", diff --git a/publish/src/app.rs b/publish/src/app.rs index 8c3d869..15de87a 100644 --- a/publish/src/app.rs +++ b/publish/src/app.rs @@ -18,7 +18,7 @@ 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, Quad, Term}; +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; @@ -56,6 +56,7 @@ pub(crate) enum Message { SearchResultClicked(NamedNode), DatatypeUpdated(QuadKey, Option), LanguageUpdated(QuadKey, Option), + SubjectUpdated(QuadKey, String), PredicateUpdated(QuadKey, String), ObjectUpdated(QuadKey, String), DirectionToggled(QuadKey, BaseDirection), @@ -129,7 +130,7 @@ impl Publisher { let mut abbreviated_datatypes = ontology .datatypes() .into_iter() - .map(|node| curie_helper.abbreviate(node.as_str()) + .map(|node| curie_helper.abbreviate(None, node.as_str()) .unwrap_or(node.as_str().to_string()) ).collect::>(); abbreviated_datatypes.sort(); @@ -238,7 +239,7 @@ impl Publisher { Schema::iri_field() => individual.as_str(), ); - if let Some(curie) = curie_helper.abbreviate(individual.as_str()) { + if let Some(curie) = curie_helper.abbreviate(None, individual.as_str()) { document.add_text(Schema::curie_field(), curie); } @@ -340,7 +341,7 @@ impl Publisher { let term = TermHelper::new(&quad.object); let datatype = term .datatype() - .map(|datatype| self.curie_helper.abbreviate(datatype.as_str()) + .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( @@ -454,7 +455,7 @@ impl Publisher { Message::DatatypeUpdated(key, Some(maybe_prefixed_iri)) => { let node = self .curie_helper - .expand(&maybe_prefixed_iri) + .expand(None, &maybe_prefixed_iri) .unwrap_or(maybe_prefixed_iri); if let Some(quad) = self.document.dataset_mut().quads.get_mut(key) { @@ -484,6 +485,12 @@ impl Publisher { self.modified = true; } } + Message::SubjectUpdated(key, value) => { + if let Some(quad) = self.document.dataset_mut().quads.get_mut(key) { + quad.subject = NamedOrBlankNode::NamedNode(NamedNode::new_unchecked(value)); + self.modified = true; + } + } Message::PredicateUpdated(key, value) => { if let Some(quad) = self.document.dataset_mut().quads.get_mut(key) { quad.predicate = NamedNode::new_unchecked(value); @@ -627,7 +634,21 @@ impl Publisher { container(space()).width(BUTTON_WIDTH) }; - let predicate_input_base = iri_input(&self.curie_helper, "Predicate", triple.predicate.as_str()); + let base = self.document.origin().as_str(); + + let subject = match &triple.subject { + NamedOrBlankNode::NamedNode(subject) => subject.as_str(), + _ => "", + }; + + let subject_input_base = iri_input(&self.curie_helper, "Subject", Some(&base), subject); + let subject_input = if self.ontology.is_read_only(triple.as_ref()) { + subject_input_base + } else { + subject_input_base.on_input(move |value| Message::SubjectUpdated(key, value)) + }; + + let predicate_input_base = iri_input(&self.curie_helper, "Predicate", Some(&base), triple.predicate.as_str()); let predicate_input = if self.ontology.is_read_only(triple.as_ref()) { predicate_input_base } else { @@ -659,7 +680,7 @@ impl Publisher { }) .unwrap_or(Horizontal::Left); - let object_input_base = iri_input(&self.curie_helper, "Object", value.as_str()) + let object_input_base = iri_input(&self.curie_helper, "Object", Some(&base), value.as_str()) .on_shift_click(Message::NavigateToObject(key)); let object_input = if self.ontology.is_read_only(triple.as_ref()) { object_input_base @@ -670,7 +691,7 @@ impl Publisher { }; let selected_datatype = term.datatype() - .and_then(|node| self.curie_helper.abbreviate(node.as_str())); + .and_then(|node| self.curie_helper.abbreviate(None, node.as_str())); let datatype_selector: Element = if state.read_only { selected_datatype.map(text).into() @@ -720,6 +741,7 @@ impl Publisher { let row = row![ button_area, + subject_input, predicate_input, predicate_label, object_input, @@ -740,7 +762,7 @@ impl Publisher { entities: impl IntoIterator, ) -> Element<'_, Message> { let buttons = entities.into_iter().map(|entity| { - let abbreviation = self.curie_helper.abbreviate(entity.iri.as_str()) + let abbreviation = self.curie_helper.abbreviate(None, entity.iri.as_str()) .unwrap_or_else(|| entity.iri.as_str().to_string()); let label = format!("{} ({})", entity.label, abbreviation); @@ -775,7 +797,7 @@ impl Publisher { .and_then(|value| value.as_str()) .unwrap_or_default(); - let abbreviated_iri = self.curie_helper.abbreviate(iri) + let abbreviated_iri = self.curie_helper.abbreviate(None, iri) .unwrap_or_else(|| iri.to_string()); button(text(abbreviated_iri).wrapping(Wrapping::Word)) @@ -830,7 +852,7 @@ impl Publisher { let reindex_ontology_button = button("Rebuild index").on_press(Message::RebuildIndex); - let address_input = iri_input(&self.curie_helper, "URL", &self.url_input) + let address_input = iri_input(&self.curie_helper, "URL", None, &self.url_input) .on_input(Message::URLInputChanged) .on_submit(Message::URLInputSubmitted) .on_control_click(Message::OpenQueryWindow(SearchResultClickAction::URLInput)); diff --git a/publish/src/rdf/curie.rs b/publish/src/rdf/curie.rs index aa53900..fe1cd30 100644 --- a/publish/src/rdf/curie.rs +++ b/publish/src/rdf/curie.rs @@ -12,7 +12,11 @@ impl CurieHelper { } } - pub fn abbreviate(&self, iri: &str) -> Option { + pub fn abbreviate(&self, base: Option<&str>, iri: &str) -> Option { + if let Some(base) = base && let Some(local_name) = iri.strip_prefix(base) { + return Some(format!(":{local_name}")); + } + for (name, base) in &self.prefixes { if let Some(local_name) = iri.strip_prefix(base) { return Some(format!("{name}:{local_name}")); @@ -21,10 +25,15 @@ impl CurieHelper { None } - pub fn expand(&self, abbreviated_iri: &str) -> Option { + pub fn expand(&self, base: Option<&str>, abbreviated_iri: &str) -> Option { let (prefix, name) = abbreviated_iri.split_once(':')?; - self.prefixes - .get(prefix) - .map(|base| format!("{base}{name}")) + + if prefix == "" && let Some(base) = base { + Some(format!("{base}{name}")) + } else { + self.prefixes + .get(prefix) + .map(|base| format!("{base}{name}")) + } } } \ No newline at end of file diff --git a/publish/src/rdf/ontology.rs b/publish/src/rdf/ontology.rs index 7db1869..af4aac5 100644 --- a/publish/src/rdf/ontology.rs +++ b/publish/src/rdf/ontology.rs @@ -29,6 +29,8 @@ static PREFIXES: LazyLock> = LazyLock::new(|| { "ebucore", "http://www.ebu.ch/metadata/ontologies/ebucore/ebucore#", ), + ("prov", "http://www.w3.org/ns/prov#"), + ("locid", "http://id.loc.gov/vocabulary/identifiers/"), ("premis", "http://www.loc.gov/premis/rdf/v1#"), ("premis3", "http://www.loc.gov/premis/rdf/v3/"), ("dcterms", "http://purl.org/dc/terms/"), diff --git a/publish/src/widget/iri_input.rs b/publish/src/widget/iri_input.rs index e7341f0..be0c144 100644 --- a/publish/src/widget/iri_input.rs +++ b/publish/src/widget/iri_input.rs @@ -20,6 +20,7 @@ where Renderer: text::Renderer, { curie_helper: &'a CurieHelper, + base: Option, on_control_click: Option, on_shift_click: Option, text_input: widget::TextInput<'a, Message, Theme, Renderer>, @@ -31,11 +32,12 @@ where Theme: Catalog, Renderer: text::Renderer, { - pub fn new(curie_helper: &'a CurieHelper, placeholder: &str, iri: &str) -> Self { - let display_value = curie_helper.abbreviate(iri).unwrap_or_else(|| iri.to_string()); + pub fn new(curie_helper: &'a CurieHelper, placeholder: &str, base: Option<&str>, iri: &str) -> Self { + let display_value = curie_helper.abbreviate(base, iri).unwrap_or_else(|| iri.to_string()); let text_input = widget::TextInput::new(placeholder, &display_value); Self { curie_helper, + base: base.map(String::from), on_control_click: None, on_shift_click: None, text_input, @@ -62,8 +64,9 @@ where #[must_use] pub fn on_input(mut self, on_input: impl Fn(String) -> Message + 'a) -> Self { + let base_clone = self.base.clone(); let wrapped = move |value: String| { - let expanded_value = self.curie_helper.expand(&value); + let expanded_value = self.curie_helper.expand(base_clone.as_deref(), &value); on_input(expanded_value.unwrap_or_else(|| value)) }; @@ -90,13 +93,13 @@ where } } -pub fn iri_input<'a, Message, Theme, Renderer>(curie_helper: &'a CurieHelper, placeholder: &str, iri: &str) -> IriInput<'a, Message, Theme, Renderer> +pub fn iri_input<'a, Message, Theme, Renderer>(curie_helper: &'a CurieHelper, placeholder: &str, base: Option<&str>, iri: &str) -> IriInput<'a, Message, Theme, Renderer> where Message: Clone, Theme: Catalog, Renderer: text::Renderer, { - IriInput::new(curie_helper, placeholder, iri) + IriInput::new(curie_helper, placeholder, base, iri) } impl Widget for IriInput<'_, Message, Theme, Renderer> @@ -160,7 +163,7 @@ where let clipboard = shell.clipboard_mut(); if let Some(Content::Text(content)) = &clipboard.write { - if let Some(expanded) = self.curie_helper.expand(&content) { + if let Some(expanded) = self.curie_helper.expand(self.base.as_deref(), &content) { clipboard.write = Some(Content::Text(expanded)); } }