.
This commit is contained in:
+44
-12
@@ -1,7 +1,7 @@
|
||||
use std::collections::HashMap;
|
||||
use crate::rdf::ontology::{IndexEntry, LabeledIri, Ontology};
|
||||
use crate::rdf::term_helper::{TermHelper, TermHelperMut};
|
||||
use gl_search::{Schema, SearchDocument, SearchIndex, doc, IndexWriter, Value};
|
||||
use gl_search::{Schema, SearchDocument, SearchIndex, doc, Value};
|
||||
use http::StatusCode;
|
||||
use iced::alignment::Horizontal;
|
||||
use iced::widget::button::Style;
|
||||
@@ -21,10 +21,12 @@ use oxigraph::model::vocab::{rdf, rdfs};
|
||||
use oxigraph::model::{BaseDirection, Dataset, NamedNode, Quad, Term};
|
||||
use tracing::{debug, debug_span, error, trace};
|
||||
use tracing::span::EnteredSpan;
|
||||
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;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) enum Message {
|
||||
@@ -37,6 +39,7 @@ pub(crate) enum Message {
|
||||
WindowClosed(window::Id),
|
||||
URLInputChanged(String),
|
||||
URLInputSubmitted,
|
||||
NavigateTo(Url),
|
||||
FetchDocument(Url),
|
||||
LoadDocument(RdfSource<KeyedDataset<RowState>>),
|
||||
ShowNewDocumentButtons,
|
||||
@@ -64,6 +67,8 @@ pub(crate) enum Message {
|
||||
NewDocument(NamedNode),
|
||||
NavigateToPredicate(QuadKey),
|
||||
NavigateToObject(QuadKey),
|
||||
NavigateBack,
|
||||
NavigateForward,
|
||||
ResetState,
|
||||
}
|
||||
|
||||
@@ -95,6 +100,7 @@ pub(crate) struct Publisher {
|
||||
abbreviated_datatypes: Vec<String>,
|
||||
window_id: window::Id,
|
||||
url_input: String,
|
||||
navigator: Navigator<Url>,
|
||||
document: RdfSource<KeyedDataset<RowState>>,
|
||||
hovered_row: Option<QuadKey>,
|
||||
search_state: Option<SearchState>,
|
||||
@@ -139,8 +145,9 @@ impl Publisher {
|
||||
))
|
||||
.build();
|
||||
|
||||
let url_input = "http://fedora.quill.lan/rest/".to_string();
|
||||
let document = RdfSource::new(Url::parse(&url_input).unwrap());
|
||||
let starting_url = Url::parse("http://fedora.quill.lan/rest/").unwrap();
|
||||
let navigator = Navigator::new(starting_url.clone());
|
||||
let document = RdfSource::new(starting_url.clone());
|
||||
|
||||
(
|
||||
Self {
|
||||
@@ -149,7 +156,8 @@ impl Publisher {
|
||||
ontology,
|
||||
abbreviated_datatypes,
|
||||
window_id: id,
|
||||
url_input,
|
||||
url_input: starting_url.to_string(),
|
||||
navigator,
|
||||
document,
|
||||
hovered_row: None,
|
||||
search_state: None,
|
||||
@@ -255,6 +263,10 @@ impl Publisher {
|
||||
}
|
||||
Message::URLInputSubmitted => {
|
||||
let url = Url::parse(&self.url_input).expect("Invalid URL");
|
||||
self.navigator.goto(url.clone());
|
||||
task = Task::done(Message::NavigateTo(url));
|
||||
}
|
||||
Message::NavigateTo(url) => {
|
||||
task = Task::done(Message::DeleteAllRows)
|
||||
.chain(Task::done(Message::ResetState))
|
||||
.chain(Task::done(Message::FetchDocument(url)));
|
||||
@@ -558,18 +570,30 @@ impl Publisher {
|
||||
}
|
||||
Message::NavigateToPredicate(key) => {
|
||||
if let Some(quad) = self.document.dataset_mut().quads.get_mut(key) {
|
||||
self.url_input = quad.predicate.as_str().to_string();
|
||||
task = Task::done(Message::URLInputSubmitted);
|
||||
let url = Url::parse(quad.predicate.as_str()).expect("Invalid URL");
|
||||
task = Task::done(Message::URLInputChanged(url.to_string()))
|
||||
.chain(Task::done(Message::URLInputSubmitted));
|
||||
}
|
||||
}
|
||||
Message::NavigateToObject(key) => {
|
||||
if let Some(quad) = self.document.dataset_mut().quads.get_mut(key) {
|
||||
if let Term::NamedNode(node) = &quad.object {
|
||||
self.url_input = node.as_str().to_string();
|
||||
task = Task::done(Message::URLInputSubmitted);
|
||||
let url = Url::parse(node.as_str()).expect("Invalid URL");
|
||||
task = Task::done(Message::URLInputChanged(url.to_string()))
|
||||
.chain(Task::done(Message::URLInputSubmitted));
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::NavigateBack => {
|
||||
let url = self.navigator.back().clone();
|
||||
task = Task::done(Message::URLInputChanged(url.to_string()))
|
||||
.chain(Task::done(Message::NavigateTo(url)));
|
||||
}
|
||||
Message::NavigateForward => {
|
||||
let url = self.navigator.forward().clone();
|
||||
task = Task::done(Message::URLInputChanged(url.to_string()))
|
||||
.chain(Task::done(Message::NavigateTo(url)));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
task
|
||||
@@ -634,14 +658,14 @@ 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", value.as_str())
|
||||
.on_shift_click(Message::NavigateToObject(key));
|
||||
let object_input = if self.ontology.is_read_only(triple.as_ref()) {
|
||||
object_input_base
|
||||
} else {
|
||||
object_input_base.align_x(value_alignment)
|
||||
.on_input(move |value| Message::ObjectUpdated(key, value))
|
||||
.on_control_click(Message::OpenQueryWindow(SearchResultClickAction::Object(key)))
|
||||
.on_shift_click(Message::NavigateToObject(key))
|
||||
};
|
||||
|
||||
let selected_datatype = term.datatype()
|
||||
@@ -797,6 +821,10 @@ impl Publisher {
|
||||
].into();
|
||||
}
|
||||
|
||||
let back_button = button("\u{21e6}").on_press(Message::NavigateBack);
|
||||
|
||||
let forward_button = button("\u{21e8}").on_press(Message::NavigateForward);
|
||||
|
||||
let add_row_button = button("Add row").on_press(Message::AddRow(None));
|
||||
|
||||
let reindex_ontology_button = button("Rebuild index").on_press(Message::RebuildIndex);
|
||||
@@ -831,8 +859,10 @@ impl Publisher {
|
||||
save_button_base
|
||||
};
|
||||
|
||||
let content = column![
|
||||
let content = navigation_area(column![
|
||||
row![
|
||||
back_button,
|
||||
forward_button,
|
||||
add_row_button,
|
||||
reindex_ontology_button,
|
||||
traverse_button,
|
||||
@@ -841,7 +871,9 @@ impl Publisher {
|
||||
],
|
||||
scrollable(body),
|
||||
space::vertical(),
|
||||
];
|
||||
])
|
||||
.on_back_click(Message::NavigateBack)
|
||||
.on_forward_click(Message::NavigateForward);
|
||||
|
||||
if self.show_overwrite_confirmation {
|
||||
let confirmation_modal = container(column![
|
||||
|
||||
@@ -3,6 +3,7 @@ mod error;
|
||||
mod rdf;
|
||||
mod windows;
|
||||
mod widget;
|
||||
mod navigator;
|
||||
|
||||
use crate::app::Publisher;
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
pub struct Navigator<T> {
|
||||
entries: Vec<T>,
|
||||
active_entry: usize,
|
||||
}
|
||||
|
||||
impl<T> Navigator<T> {
|
||||
pub fn new(start: T) -> Self {
|
||||
Self {
|
||||
entries: vec![start],
|
||||
active_entry: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn goto(&mut self, target: T) {
|
||||
self.entries.push(target);
|
||||
self.active_entry += 1;
|
||||
}
|
||||
|
||||
pub fn current(&self) -> &T {
|
||||
&self.entries[self.active_entry]
|
||||
}
|
||||
|
||||
pub fn back(&mut self) -> &T {
|
||||
if self.active_entry > 0 {
|
||||
self.active_entry -= 1;
|
||||
}
|
||||
self.current()
|
||||
}
|
||||
|
||||
pub fn forward(&mut self) -> &T {
|
||||
if self.active_entry < self.entries.len() - 1 {
|
||||
self.active_entry += 1;
|
||||
}
|
||||
self.current()
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::error;
|
||||
use crate::rdf::vocab::gl;
|
||||
use oxigraph::model::vocab::{rdf, rdfs, xsd};
|
||||
use oxigraph::model::{Dataset, Graph, GraphName, GraphNameRef, LiteralRef, NamedNode, NamedNodeRef, NamedOrBlankNode, NamedOrBlankNodeRef, Term, TermRef, Triple, TripleRef};
|
||||
use oxigraph::model::{Dataset, Graph, GraphNameRef, LiteralRef, NamedNode, NamedNodeRef, NamedOrBlankNode, NamedOrBlankNodeRef, Term, TermRef, Triple, TripleRef};
|
||||
use oxigraph::sparql::{PreparedSparqlQuery, QueryResults, SparqlEvaluator};
|
||||
use std::collections::{BTreeMap, BTreeSet, HashMap};
|
||||
use std::fmt::Display;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use std::collections::BTreeMap;
|
||||
use iced::{alignment, keyboard, widget, Element, Event, Length, Rectangle, Size};
|
||||
use iced::advanced::{mouse, text, Shell};
|
||||
use iced::advanced::layout::{Limits, Node};
|
||||
@@ -7,6 +6,7 @@ use iced::advanced::{Layout, Widget};
|
||||
use iced::advanced::widget::{tree, Tree};
|
||||
use iced::mouse::{Cursor, Interaction};
|
||||
use iced::widget::text_input::Catalog;
|
||||
use tracing::debug;
|
||||
use crate::rdf::curie::CurieHelper;
|
||||
|
||||
pub struct State {
|
||||
@@ -42,21 +42,25 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn align_x(mut self, alignment: impl Into<alignment::Horizontal>) -> Self {
|
||||
self.text_input = self.text_input.align_x(alignment);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn on_control_click(mut self, on_control_click: Message) -> Self {
|
||||
self.on_control_click = Some(on_control_click);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn on_shift_click(mut self, on_shift_click: Message) -> Self {
|
||||
self.on_shift_click = Some(on_shift_click);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn on_input(mut self, on_input: impl Fn(String) -> Message + 'a) -> Self {
|
||||
let wrapped = move |value: String| {
|
||||
let expanded_value = self.curie_helper.expand(&value);
|
||||
@@ -67,6 +71,7 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn on_submit(mut self, message: Message) -> Self {
|
||||
self.text_input = self.text_input.on_submit(message);
|
||||
self
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
pub mod iri_input;
|
||||
pub mod iri_input;
|
||||
pub mod navigation_area;
|
||||
@@ -0,0 +1,137 @@
|
||||
use iced::advanced::{mouse, text, Layout, Shell, Widget};
|
||||
use iced::{Element, Event, Length, Rectangle, Size};
|
||||
use iced::advanced::layout::{Limits, Node};
|
||||
use iced::advanced::renderer::Style;
|
||||
use iced::advanced::widget::{Operation, Tree};
|
||||
use iced::mouse::Cursor;
|
||||
use iced::widget::text_input::Catalog;
|
||||
|
||||
pub struct NavigationArea<'a, Message, Theme, Renderer>
|
||||
where
|
||||
Theme: Catalog,
|
||||
Renderer: text::Renderer,
|
||||
{
|
||||
content: Element<'a, Message, Theme, Renderer>,
|
||||
on_back_click: Option<Message>,
|
||||
on_forward_click: Option<Message>,
|
||||
}
|
||||
|
||||
impl<'a, Message, Theme, Renderer> NavigationArea<'a, Message, Theme, Renderer>
|
||||
where
|
||||
Message: Clone,
|
||||
Theme: Catalog,
|
||||
Renderer: text::Renderer,
|
||||
{
|
||||
#[must_use]
|
||||
pub fn new(content: impl Into<Element<'a, Message, Theme, Renderer>>) -> Self {
|
||||
Self {
|
||||
content: content.into(),
|
||||
on_back_click: None,
|
||||
on_forward_click: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn on_back_click(mut self, message: Message) -> Self {
|
||||
self.on_back_click = Some(message);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn on_forward_click(mut self, message: Message) -> Self {
|
||||
self.on_forward_click = Some(message);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Theme, Renderer> From<NavigationArea<'a, Message, Theme, Renderer>>
|
||||
for Element<'a, Message, Theme, Renderer>
|
||||
where
|
||||
Message: Clone + 'a,
|
||||
Theme: Catalog + 'a,
|
||||
Renderer: text::Renderer + 'a,
|
||||
{
|
||||
fn from(value: NavigationArea<'a, Message, Theme, Renderer>) -> Self {
|
||||
Element::new(value)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn navigation_area<'a, Message, Theme, Renderer>(content: impl Into<Element<'a, Message, Theme, Renderer>>) -> NavigationArea<'a, Message, Theme, Renderer>
|
||||
where
|
||||
Message: Clone,
|
||||
Theme: Catalog,
|
||||
Renderer: text::Renderer,
|
||||
{
|
||||
NavigationArea::new(content)
|
||||
}
|
||||
|
||||
impl <Message, Theme, Renderer> Widget<Message, Theme, Renderer> for NavigationArea<'_, Message, Theme, Renderer>
|
||||
where
|
||||
Message: Clone,
|
||||
Theme: Catalog,
|
||||
Renderer: text::Renderer,
|
||||
{
|
||||
fn size(&self) -> Size<Length> {
|
||||
self.content.as_widget().size()
|
||||
}
|
||||
|
||||
fn layout(&mut self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node {
|
||||
self.content
|
||||
.as_widget_mut()
|
||||
.layout(&mut tree.children[0], renderer, limits)
|
||||
}
|
||||
|
||||
fn draw(&self, tree: &Tree, renderer: &mut Renderer, theme: &Theme, style: &Style, layout: Layout<'_>, cursor: Cursor, viewport: &Rectangle) {
|
||||
self.content.as_widget().draw(
|
||||
&tree.children[0],
|
||||
renderer,
|
||||
theme,
|
||||
style,
|
||||
layout,
|
||||
cursor,
|
||||
viewport,
|
||||
);
|
||||
}
|
||||
|
||||
fn diff(&mut self, tree: &mut Tree) {
|
||||
tree.diff_children(std::slice::from_mut(&mut self.content));
|
||||
}
|
||||
|
||||
fn operate(&mut self, tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, operation: &mut dyn Operation) {
|
||||
self.content
|
||||
.as_widget_mut()
|
||||
.operate(&mut tree.children[0], layout, renderer, operation);
|
||||
}
|
||||
|
||||
fn update(&mut self, tree: &mut Tree, event: &Event, layout: Layout<'_>, cursor: Cursor, renderer: &Renderer, shell: &mut Shell<'_, Message>, viewport: &Rectangle) {
|
||||
self.content.as_widget_mut().update(
|
||||
&mut tree.children[0],
|
||||
event,
|
||||
layout,
|
||||
cursor,
|
||||
renderer,
|
||||
shell,
|
||||
viewport,
|
||||
);
|
||||
|
||||
if shell.is_event_captured() {
|
||||
return;
|
||||
}
|
||||
|
||||
match event {
|
||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Back)) => {
|
||||
if let Some(on_back_click) = &self.on_back_click {
|
||||
shell.publish(on_back_click.clone());
|
||||
shell.capture_event();
|
||||
}
|
||||
}
|
||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Forward)) => {
|
||||
if let Some(on_forward_click) = &self.on_forward_click {
|
||||
shell.publish(on_forward_click.clone());
|
||||
shell.capture_event();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user