diff --git a/publish/src/app.rs b/publish/src/app.rs index cd30cc8..5ec76b3 100644 --- a/publish/src/app.rs +++ b/publish/src/app.rs @@ -8,10 +8,7 @@ use iced::widget::button::Style; use iced::widget::grid::Sizing; use iced::widget::{button, center, column, combo_box, container, grid, mouse_area, opaque, 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, keyboard}; -//use iced::advanced::Widget; -use iced::keyboard::{Event, Key}; -use iced::keyboard::key::Named::Control; +use iced::{Background, Color, Element, Length, Subscription, Task, color, window}; use iced::widget::text::Wrapping; use ldp::middleware::BasicAuthMiddleware; use ldp::model::{KeyedDataset, QuadKey}; @@ -28,7 +25,6 @@ use crate::widget::iri_input::iri_input; #[derive(Debug, Clone)] pub(crate) enum Message { None, - Foo(QuadKey), Traverse, IndexRdfSource(RdfSource), CommitIndex, @@ -554,7 +550,8 @@ impl Publisher { let value = term .value_as_named_node() - .map(|node| self.ontology.abbreviate(node)) + //.map(|node| self.ontology.abbreviate(node)) + .map(|node| node.as_str().to_string()) .unwrap_or(term.value().to_string()); let value_alignment = term @@ -565,10 +562,10 @@ impl Publisher { }) .unwrap_or(Horizontal::Left); - let value_input_base = text_input("Value", value.as_str()) - .align_x(value_alignment); + /*let value_input_base = text_input("Value", value.as_str()) + .align_x(value_alignment);*/ - let value_input = if !state.read_only { + /*let value_input = if !state.read_only { let is_named_node = term.is_named_node(); value_input_base.on_input(move |input| { let expanded_input = if is_named_node { @@ -584,12 +581,11 @@ impl Publisher { }) } else { value_input_base - }; + };*/ - let foo = iri_input(value_input) - .on_control_click(Message::OpenQueryWindow(SearchResultClickAction::Object( - key, - ))); + let foo = iri_input(self.ontology.prefixes(), "Object", value.as_str()) + .on_input(move |value| Message::ValueUpdated(key, value)) + .on_control_click(Message::OpenQueryWindow(SearchResultClickAction::Object(key))); /*let search_launcher = if term.is_named_node() && !state.read_only { Some(container( diff --git a/publish/src/rdf/ontology.rs b/publish/src/rdf/ontology.rs index 5a53060..c3b0bee 100644 --- a/publish/src/rdf/ontology.rs +++ b/publish/src/rdf/ontology.rs @@ -4,43 +4,46 @@ use oxigraph::io::{RdfFormat, RdfParser}; use oxigraph::model::vocab::{rdf, rdfs, xsd}; use oxigraph::model::{Dataset, GraphName, LiteralRef, NamedNode, NamedNodeRef, NamedOrBlankNode, NamedOrBlankNodeRef, Quad, Term, TermRef, Triple, TripleRef}; use oxigraph::sparql::{QueryResults, SparqlEvaluator}; -use std::collections::{HashMap, HashSet}; +use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::fmt::Display; use std::path::{Path, PathBuf}; +use std::sync::LazyLock; use oxigraph::store::Store; use crate::rdf::materialize; -const PREFIXES: &[(&str, &str)] = &[ - ("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"), - ("rdfs", "http://www.w3.org/2000/01/rdf-schema#"), - ("owl", "http://www.w3.org/2002/07/owl#"), - ("xsd", "http://www.w3.org/2001/XMLSchema#"), - ("ldp", "http://www.w3.org/ns/ldp#"), - ("dc", "http://purl.org/dc/elements/1.1/"), - ("posix", "http://www.w3.org/ns/posix/stat#"), - ( - "ebucore", - "http://www.ebu.ch/metadata/ontologies/ebucore/ebucore#", - ), - ("premis", "http://www.loc.gov/premis/rdf/v1#"), - ("premis3", "http://www.loc.gov/premis/rdf/v3/"), - ("dcterms", "http://purl.org/dc/terms/"), - ("fedora", "http://fedora.info/definitions/v4/repository#"), - ("rdaa", "http://rdaregistry.info/Elements/a/"), - ("rdaad", "http://rdaregistry.info/Elements/a/datatype/"), - ("rdac", "http://rdaregistry.info/Elements/c/"), - ("rdae", "http://rdaregistry.info/Elements/e/"), - ("rdai", "http://rdaregistry.info/Elements/i/"), - ("rdam", "http://rdaregistry.info/Elements/m/"), - ("rdan", "http://rdaregistry.info/Elements/n/"), - ("rdap", "http://rdaregistry.info/Elements/p/"), - ("rdat", "http://rdaregistry.info/Elements/t/"), - ("rdaw", "http://rdaregistry.info/Elements/w/"), - ("rdax", "http://rdaregistry.info/Elements/x/"), - ("schema", "https://schema.org/"), - ("quill", "http://fedora.quill.lan/rest/"), - ("gl", "https://graphofliberty.org/2026/04/ont/"), -]; +static PREFIXES: LazyLock> = LazyLock::new(|| { + BTreeMap::from_iter([ + ("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"), + ("rdfs", "http://www.w3.org/2000/01/rdf-schema#"), + ("owl", "http://www.w3.org/2002/07/owl#"), + ("xsd", "http://www.w3.org/2001/XMLSchema#"), + ("ldp", "http://www.w3.org/ns/ldp#"), + ("dc", "http://purl.org/dc/elements/1.1/"), + ("posix", "http://www.w3.org/ns/posix/stat#"), + ( + "ebucore", + "http://www.ebu.ch/metadata/ontologies/ebucore/ebucore#", + ), + ("premis", "http://www.loc.gov/premis/rdf/v1#"), + ("premis3", "http://www.loc.gov/premis/rdf/v3/"), + ("dcterms", "http://purl.org/dc/terms/"), + ("fedora", "http://fedora.info/definitions/v4/repository#"), + ("rdaa", "http://rdaregistry.info/Elements/a/"), + ("rdaad", "http://rdaregistry.info/Elements/a/datatype/"), + ("rdac", "http://rdaregistry.info/Elements/c/"), + ("rdae", "http://rdaregistry.info/Elements/e/"), + ("rdai", "http://rdaregistry.info/Elements/i/"), + ("rdam", "http://rdaregistry.info/Elements/m/"), + ("rdan", "http://rdaregistry.info/Elements/n/"), + ("rdap", "http://rdaregistry.info/Elements/p/"), + ("rdat", "http://rdaregistry.info/Elements/t/"), + ("rdaw", "http://rdaregistry.info/Elements/w/"), + ("rdax", "http://rdaregistry.info/Elements/x/"), + ("schema", "https://schema.org/"), + ("quill", "http://fedora.quill.lan/rest/"), + ("gl", "https://graphofliberty.org/2026/04/ont/"), + ].map(|(k, v)| (k.to_string(), v.to_string()))) +}); pub struct OntologyBuilder { path: Option, @@ -256,6 +259,10 @@ impl Ontology { } } + pub fn prefixes(&self) -> &BTreeMap { + &*PREFIXES + } + /*pub fn info(&self, node: NamedNodeRef<'_>) -> Option<&IriInformation> { let node = node.into_owned(); self.iri_info.get(&node) diff --git a/publish/src/widget/iri_input.rs b/publish/src/widget/iri_input.rs index bbe6854..28b613c 100644 --- a/publish/src/widget/iri_input.rs +++ b/publish/src/widget/iri_input.rs @@ -1,3 +1,4 @@ +use std::collections::BTreeMap; use iced::{keyboard, Element, Event, Length, Rectangle, Size}; use iced::advanced::{mouse, text, Shell}; use iced::advanced::layout::{Limits, Node}; @@ -7,28 +8,79 @@ use iced::advanced::widget::{tree, Tree}; use iced::mouse::{Cursor, Interaction}; use iced::widget::text_input::Catalog; use iced::widget::TextInput; +use tracing::info; pub struct State { + value: String, control: bool, } -pub struct IriInput<'a, Message, Theme = iced::Theme, Renderer = iced::Renderer> -where -Theme: Catalog, -Renderer: text::Renderer { - text_input: TextInput<'a, Message, Theme, Renderer>, - on_control_click: Option, -} - -impl<'a, Message, Theme, Renderer> IriInput<'a, Message, Theme, Renderer> +pub struct IriInput<'a, Message, Theme, Renderer> where Theme: Catalog, Renderer: text::Renderer, { + prefixes: &'a BTreeMap, + placeholder: String, + iri: String, + on_control_click: Option, + on_input: Option Message + 'a>>, + text_input: TextInput<'a, Message, Theme, Renderer>, +} + +fn abbreviate(prefixes: &BTreeMap, iri: &str) -> Option { + for (name, base) in prefixes { + if let Some(local_name) = iri.strip_prefix(base) { + return if local_name.is_empty() { + Some(format!("{name}:")) + } else { + Some(format!("{name}:{local_name}")) + }; + } + } + None +} + +fn expand(prefixes: &BTreeMap, abbreviated_iri: &str) -> Option { + let (prefix, name) = abbreviated_iri.split_once(':')?; + prefixes + .get(prefix) + .map(|base| format!("{base}{name}")) +} + +impl<'a, Message, Theme, Renderer> IriInput<'a, Message, Theme, Renderer> +where + Message: Clone, + Theme: Catalog, + Renderer: text::Renderer, +{ + pub fn new(prefixes: &'a BTreeMap, placeholder: &str, iri: &str) -> Self { + let display_value = abbreviate(prefixes, iri).unwrap_or_else(|| iri.to_string()); + let text_input = TextInput::new(placeholder, &display_value); + Self { + prefixes, + placeholder: placeholder.to_string(), + iri: iri.to_string(), + on_control_click: None, + on_input: None, + text_input, + } + } pub fn on_control_click(mut self, on_control_click: Message) -> Self { self.on_control_click = Some(on_control_click); self } + + pub fn on_input(mut self, on_input: impl Fn(String) -> Message + 'a) -> Self { + let iri = self.iri.clone(); + let wrapped = move |value: String| { + let expanded_value = expand(self.prefixes, &value); + on_input(expanded_value.unwrap_or_else(|| iri.clone())) + }; + + self.text_input = self.text_input.on_input(wrapped); + self + } } impl<'a, Message, Theme, Renderer> From> @@ -43,16 +95,13 @@ where } } -pub fn iri_input(text_input: TextInput) -> IriInput +pub fn iri_input<'a, Message, Theme, Renderer>(prefixes: &'a BTreeMap, placeholder: &str, iri: &str) -> IriInput<'a, Message, Theme, Renderer> where Message: Clone, Theme: Catalog, Renderer: text::Renderer, { - IriInput { - text_input, - on_control_click: None, - } + IriInput::new(prefixes, placeholder, iri) } impl Widget for IriInput<'_, Message, Theme, Renderer> @@ -64,6 +113,7 @@ where fn size(&self) -> Size { Widget::size(&self.text_input) } + fn layout(&mut self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node { Widget::layout(&mut self.text_input, &mut tree.children[0], renderer, limits) } @@ -78,6 +128,7 @@ where fn state(&self) -> tree::State { tree::State::new(State { + value: String::new(), control: false, }) } @@ -96,6 +147,12 @@ where Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { if cursor.is_over(layout.bounds()) && state.control && let Some(on_control_click) = &self.on_control_click { shell.publish(on_control_click.clone()); + shell.capture_event(); + } + } + Event::Keyboard(keyboard::Event::KeyPressed { text, .. }) => { + if let Some(text) = text { + state.value = text.to_string(); } } _ => {} @@ -104,12 +161,12 @@ where Widget::update(&mut self.text_input, &mut tree.children[0], event, layout, cursor, renderer, shell, viewport) } - fn mouse_interaction(&self, tree: &Tree, layout: Layout<'_>, cursor: Cursor, _viewport: &Rectangle, _renderer: &Renderer) -> Interaction { + fn mouse_interaction(&self, tree: &Tree, layout: Layout<'_>, cursor: Cursor, viewport: &Rectangle, renderer: &Renderer) -> Interaction { let state = tree.state.downcast_ref::(); if cursor.is_over(layout.bounds()) && state.control { Interaction::Pointer } else { - Interaction::None + Widget::mouse_interaction(&self.text_input, tree, layout, cursor, viewport, renderer) } } } \ No newline at end of file