.
This commit is contained in:
+44
-12
@@ -1,7 +1,7 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use crate::rdf::ontology::{IndexEntry, LabeledIri, Ontology};
|
use crate::rdf::ontology::{IndexEntry, LabeledIri, Ontology};
|
||||||
use crate::rdf::term_helper::{TermHelper, TermHelperMut};
|
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 http::StatusCode;
|
||||||
use iced::alignment::Horizontal;
|
use iced::alignment::Horizontal;
|
||||||
use iced::widget::button::Style;
|
use iced::widget::button::Style;
|
||||||
@@ -21,10 +21,12 @@ use oxigraph::model::vocab::{rdf, rdfs};
|
|||||||
use oxigraph::model::{BaseDirection, Dataset, NamedNode, Quad, Term};
|
use oxigraph::model::{BaseDirection, Dataset, NamedNode, Quad, Term};
|
||||||
use tracing::{debug, debug_span, error, trace};
|
use tracing::{debug, debug_span, error, trace};
|
||||||
use tracing::span::EnteredSpan;
|
use tracing::span::EnteredSpan;
|
||||||
|
use crate::navigator::Navigator;
|
||||||
use crate::rdf::curie::CurieHelper;
|
use crate::rdf::curie::CurieHelper;
|
||||||
use crate::rdf::language;
|
use crate::rdf::language;
|
||||||
use crate::rdf::vocab::rda;
|
use crate::rdf::vocab::rda;
|
||||||
use crate::widget::iri_input::iri_input;
|
use crate::widget::iri_input::iri_input;
|
||||||
|
use crate::widget::navigation_area::navigation_area;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) enum Message {
|
pub(crate) enum Message {
|
||||||
@@ -37,6 +39,7 @@ pub(crate) enum Message {
|
|||||||
WindowClosed(window::Id),
|
WindowClosed(window::Id),
|
||||||
URLInputChanged(String),
|
URLInputChanged(String),
|
||||||
URLInputSubmitted,
|
URLInputSubmitted,
|
||||||
|
NavigateTo(Url),
|
||||||
FetchDocument(Url),
|
FetchDocument(Url),
|
||||||
LoadDocument(RdfSource<KeyedDataset<RowState>>),
|
LoadDocument(RdfSource<KeyedDataset<RowState>>),
|
||||||
ShowNewDocumentButtons,
|
ShowNewDocumentButtons,
|
||||||
@@ -64,6 +67,8 @@ pub(crate) enum Message {
|
|||||||
NewDocument(NamedNode),
|
NewDocument(NamedNode),
|
||||||
NavigateToPredicate(QuadKey),
|
NavigateToPredicate(QuadKey),
|
||||||
NavigateToObject(QuadKey),
|
NavigateToObject(QuadKey),
|
||||||
|
NavigateBack,
|
||||||
|
NavigateForward,
|
||||||
ResetState,
|
ResetState,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,6 +100,7 @@ pub(crate) struct Publisher {
|
|||||||
abbreviated_datatypes: Vec<String>,
|
abbreviated_datatypes: Vec<String>,
|
||||||
window_id: window::Id,
|
window_id: window::Id,
|
||||||
url_input: String,
|
url_input: String,
|
||||||
|
navigator: Navigator<Url>,
|
||||||
document: RdfSource<KeyedDataset<RowState>>,
|
document: RdfSource<KeyedDataset<RowState>>,
|
||||||
hovered_row: Option<QuadKey>,
|
hovered_row: Option<QuadKey>,
|
||||||
search_state: Option<SearchState>,
|
search_state: Option<SearchState>,
|
||||||
@@ -139,8 +145,9 @@ impl Publisher {
|
|||||||
))
|
))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let url_input = "http://fedora.quill.lan/rest/".to_string();
|
let starting_url = Url::parse("http://fedora.quill.lan/rest/").unwrap();
|
||||||
let document = RdfSource::new(Url::parse(&url_input).unwrap());
|
let navigator = Navigator::new(starting_url.clone());
|
||||||
|
let document = RdfSource::new(starting_url.clone());
|
||||||
|
|
||||||
(
|
(
|
||||||
Self {
|
Self {
|
||||||
@@ -149,7 +156,8 @@ impl Publisher {
|
|||||||
ontology,
|
ontology,
|
||||||
abbreviated_datatypes,
|
abbreviated_datatypes,
|
||||||
window_id: id,
|
window_id: id,
|
||||||
url_input,
|
url_input: starting_url.to_string(),
|
||||||
|
navigator,
|
||||||
document,
|
document,
|
||||||
hovered_row: None,
|
hovered_row: None,
|
||||||
search_state: None,
|
search_state: None,
|
||||||
@@ -255,6 +263,10 @@ impl Publisher {
|
|||||||
}
|
}
|
||||||
Message::URLInputSubmitted => {
|
Message::URLInputSubmitted => {
|
||||||
let url = Url::parse(&self.url_input).expect("Invalid URL");
|
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)
|
task = Task::done(Message::DeleteAllRows)
|
||||||
.chain(Task::done(Message::ResetState))
|
.chain(Task::done(Message::ResetState))
|
||||||
.chain(Task::done(Message::FetchDocument(url)));
|
.chain(Task::done(Message::FetchDocument(url)));
|
||||||
@@ -558,18 +570,30 @@ impl Publisher {
|
|||||||
}
|
}
|
||||||
Message::NavigateToPredicate(key) => {
|
Message::NavigateToPredicate(key) => {
|
||||||
if let Some(quad) = self.document.dataset_mut().quads.get_mut(key) {
|
if let Some(quad) = self.document.dataset_mut().quads.get_mut(key) {
|
||||||
self.url_input = quad.predicate.as_str().to_string();
|
let url = Url::parse(quad.predicate.as_str()).expect("Invalid URL");
|
||||||
task = Task::done(Message::URLInputSubmitted);
|
task = Task::done(Message::URLInputChanged(url.to_string()))
|
||||||
|
.chain(Task::done(Message::URLInputSubmitted));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::NavigateToObject(key) => {
|
Message::NavigateToObject(key) => {
|
||||||
if let Some(quad) = self.document.dataset_mut().quads.get_mut(key) {
|
if let Some(quad) = self.document.dataset_mut().quads.get_mut(key) {
|
||||||
if let Term::NamedNode(node) = &quad.object {
|
if let Term::NamedNode(node) = &quad.object {
|
||||||
self.url_input = node.as_str().to_string();
|
let url = Url::parse(node.as_str()).expect("Invalid URL");
|
||||||
task = Task::done(Message::URLInputSubmitted);
|
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
|
task
|
||||||
@@ -634,14 +658,14 @@ impl Publisher {
|
|||||||
})
|
})
|
||||||
.unwrap_or(Horizontal::Left);
|
.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()) {
|
let object_input = if self.ontology.is_read_only(triple.as_ref()) {
|
||||||
object_input_base
|
object_input_base
|
||||||
} else {
|
} else {
|
||||||
object_input_base.align_x(value_alignment)
|
object_input_base.align_x(value_alignment)
|
||||||
.on_input(move |value| Message::ObjectUpdated(key, value))
|
.on_input(move |value| Message::ObjectUpdated(key, value))
|
||||||
.on_control_click(Message::OpenQueryWindow(SearchResultClickAction::Object(key)))
|
.on_control_click(Message::OpenQueryWindow(SearchResultClickAction::Object(key)))
|
||||||
.on_shift_click(Message::NavigateToObject(key))
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let selected_datatype = term.datatype()
|
let selected_datatype = term.datatype()
|
||||||
@@ -797,6 +821,10 @@ impl Publisher {
|
|||||||
].into();
|
].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 add_row_button = button("Add row").on_press(Message::AddRow(None));
|
||||||
|
|
||||||
let reindex_ontology_button = button("Rebuild index").on_press(Message::RebuildIndex);
|
let reindex_ontology_button = button("Rebuild index").on_press(Message::RebuildIndex);
|
||||||
@@ -831,8 +859,10 @@ impl Publisher {
|
|||||||
save_button_base
|
save_button_base
|
||||||
};
|
};
|
||||||
|
|
||||||
let content = column![
|
let content = navigation_area(column![
|
||||||
row![
|
row![
|
||||||
|
back_button,
|
||||||
|
forward_button,
|
||||||
add_row_button,
|
add_row_button,
|
||||||
reindex_ontology_button,
|
reindex_ontology_button,
|
||||||
traverse_button,
|
traverse_button,
|
||||||
@@ -841,7 +871,9 @@ impl Publisher {
|
|||||||
],
|
],
|
||||||
scrollable(body),
|
scrollable(body),
|
||||||
space::vertical(),
|
space::vertical(),
|
||||||
];
|
])
|
||||||
|
.on_back_click(Message::NavigateBack)
|
||||||
|
.on_forward_click(Message::NavigateForward);
|
||||||
|
|
||||||
if self.show_overwrite_confirmation {
|
if self.show_overwrite_confirmation {
|
||||||
let confirmation_modal = container(column![
|
let confirmation_modal = container(column![
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ mod error;
|
|||||||
mod rdf;
|
mod rdf;
|
||||||
mod windows;
|
mod windows;
|
||||||
mod widget;
|
mod widget;
|
||||||
|
mod navigator;
|
||||||
|
|
||||||
use crate::app::Publisher;
|
use crate::app::Publisher;
|
||||||
use tracing_subscriber::layer::SubscriberExt;
|
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::error;
|
||||||
use crate::rdf::vocab::gl;
|
use crate::rdf::vocab::gl;
|
||||||
use oxigraph::model::vocab::{rdf, rdfs, xsd};
|
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 oxigraph::sparql::{PreparedSparqlQuery, QueryResults, SparqlEvaluator};
|
||||||
use std::collections::{BTreeMap, BTreeSet, HashMap};
|
use std::collections::{BTreeMap, BTreeSet, HashMap};
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use std::collections::BTreeMap;
|
|
||||||
use iced::{alignment, keyboard, widget, Element, Event, Length, Rectangle, Size};
|
use iced::{alignment, keyboard, widget, Element, Event, Length, Rectangle, Size};
|
||||||
use iced::advanced::{mouse, text, Shell};
|
use iced::advanced::{mouse, text, Shell};
|
||||||
use iced::advanced::layout::{Limits, Node};
|
use iced::advanced::layout::{Limits, Node};
|
||||||
@@ -7,6 +6,7 @@ use iced::advanced::{Layout, Widget};
|
|||||||
use iced::advanced::widget::{tree, Tree};
|
use iced::advanced::widget::{tree, Tree};
|
||||||
use iced::mouse::{Cursor, Interaction};
|
use iced::mouse::{Cursor, Interaction};
|
||||||
use iced::widget::text_input::Catalog;
|
use iced::widget::text_input::Catalog;
|
||||||
|
use tracing::debug;
|
||||||
use crate::rdf::curie::CurieHelper;
|
use crate::rdf::curie::CurieHelper;
|
||||||
|
|
||||||
pub struct State {
|
pub struct State {
|
||||||
@@ -42,21 +42,25 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn align_x(mut self, alignment: impl Into<alignment::Horizontal>) -> Self {
|
pub fn align_x(mut self, alignment: impl Into<alignment::Horizontal>) -> Self {
|
||||||
self.text_input = self.text_input.align_x(alignment);
|
self.text_input = self.text_input.align_x(alignment);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn on_control_click(mut self, on_control_click: Message) -> Self {
|
pub fn on_control_click(mut self, on_control_click: Message) -> Self {
|
||||||
self.on_control_click = Some(on_control_click);
|
self.on_control_click = Some(on_control_click);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn on_shift_click(mut self, on_shift_click: Message) -> Self {
|
pub fn on_shift_click(mut self, on_shift_click: Message) -> Self {
|
||||||
self.on_shift_click = Some(on_shift_click);
|
self.on_shift_click = Some(on_shift_click);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn on_input(mut self, on_input: impl Fn(String) -> Message + 'a) -> Self {
|
pub fn on_input(mut self, on_input: impl Fn(String) -> Message + 'a) -> Self {
|
||||||
let wrapped = move |value: String| {
|
let wrapped = move |value: String| {
|
||||||
let expanded_value = self.curie_helper.expand(&value);
|
let expanded_value = self.curie_helper.expand(&value);
|
||||||
@@ -67,6 +71,7 @@ where
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn on_submit(mut self, message: Message) -> Self {
|
pub fn on_submit(mut self, message: Message) -> Self {
|
||||||
self.text_input = self.text_input.on_submit(message);
|
self.text_input = self.text_input.on_submit(message);
|
||||||
self
|
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