@@ -15,11 +15,11 @@ use std::rc::Rc;
1515use derivative:: Derivative ;
1616use itertools:: Itertools ;
1717use web_sys:: { FocusEvent , HtmlInputElement , KeyboardEvent } ;
18- use yew:: { classes, function_component , html, Callback , Html , Properties , TargetCast } ;
18+ use yew:: { classes, html, Callback , Component , Html , NodeRef , Properties , TargetCast } ;
1919
2020use super :: type_icon:: TypeIconType ;
21- use crate :: clone;
2221use crate :: components:: type_icon:: TypeIcon ;
22+ use crate :: maybe;
2323use crate :: session:: Session ;
2424
2525#[ derive( PartialEq , Properties , Derivative , Clone ) ]
@@ -35,6 +35,19 @@ pub struct EditableHeaderProps {
3535 #[ derivative( Debug = "ignore" ) ]
3636 pub session : Session ,
3737}
38+ impl EditableHeaderProps {
39+ fn split_placeholder ( & self ) -> String {
40+ let split = self
41+ . placeholder
42+ . split_once ( '\n' )
43+ . map ( |( a, _) | a)
44+ . unwrap_or ( & * self . placeholder ) ;
45+ match split. char_indices ( ) . nth ( 25 ) {
46+ None => split. to_string ( ) ,
47+ Some ( ( idx, _) ) => split[ ..idx] . to_owned ( ) ,
48+ }
49+ }
50+ }
3851
3952#[ derive( Default , Debug , PartialEq , Copy , Clone ) ]
4053pub enum ValueState {
@@ -43,132 +56,132 @@ pub enum ValueState {
4356 Edited ,
4457}
4558
46- #[ function_component( EditableHeader ) ]
47- pub fn editable_header ( p : & EditableHeaderProps ) -> Html {
48- let noderef = yew:: use_node_ref ( ) ;
49- let value_state = yew:: use_state_eq ( || ValueState :: Unedited ) ;
50- let valid = yew:: use_state_eq ( || true ) ;
51- let new_value = yew:: use_state_eq ( || p. initial_value . clone ( ) ) ;
59+ pub enum EditableHeaderMsg {
60+ SetNewValue ( String ) ,
61+ OnClick ( ( ) ) ,
62+ }
63+
64+ #[ derive( Default , Debug ) ]
65+ pub struct EditableHeader {
66+ noderef : NodeRef ,
67+ edited : bool ,
68+ valid : bool ,
69+ value : Option < String > ,
70+ placeholder : String ,
71+ }
72+ impl Component for EditableHeader {
73+ type Message = EditableHeaderMsg ;
74+ type Properties = EditableHeaderProps ;
5275
53- {
54- clone ! ( new_value, p. initial_value) ;
55- yew:: use_effect_with ( p. reset_count , move |_| new_value. set ( initial_value. clone ( ) ) ) ;
76+ fn create ( ctx : & yew:: prelude:: Context < Self > ) -> Self {
77+ Self {
78+ value : ctx. props ( ) . initial_value . clone ( ) ,
79+ placeholder : ctx. props ( ) . split_placeholder ( ) ,
80+ valid : true ,
81+ ..Self :: default ( )
82+ }
5683 }
57- {
58- clone ! ( value_state. setter( ) ) ;
59- yew:: use_effect_with ( p. initial_value . clone ( ) , move |_| {
60- setter. set ( ValueState :: Unedited ) ;
61- } )
84+
85+ fn changed ( & mut self , ctx : & yew:: prelude:: Context < Self > , old_props : & Self :: Properties ) -> bool {
86+ if ctx. props ( ) . reset_count != old_props. reset_count {
87+ self . value = ctx. props ( ) . initial_value . clone ( ) ;
88+ }
89+ if ctx. props ( ) . initial_value != old_props. initial_value {
90+ self . edited = false ;
91+ self . value = ctx. props ( ) . initial_value . clone ( ) ;
92+ }
93+ if !ctx. props ( ) . editable {
94+ self . edited = false ;
95+ }
96+ self . placeholder = ctx. props ( ) . split_placeholder ( ) ;
97+ ctx. props ( ) != old_props
6298 }
6399
64- let set_new_value = yew:: use_callback (
65- (
66- new_value. clone ( ) ,
67- p. on_change . clone ( ) ,
68- p. initial_value . clone ( ) ,
69- value_state. clone ( ) ,
70- valid. clone ( ) ,
71- p. session . clone ( ) ,
72- ) ,
73- |s : String , ( new_value, on_change, initial_value, value_state, valid, session) | {
74- let maybe_s = ( !s. is_empty ( ) ) . then_some ( s) ;
75- if maybe_s == * initial_value {
76- value_state. set ( ValueState :: Unedited ) ;
77- } else {
78- value_state. set ( ValueState :: Edited ) ;
79- }
80-
81- let title_is_valid = initial_value == & maybe_s
82- || maybe_s
83- . as_ref ( )
84- . and_then ( |s| {
85- let metadata = session. metadata ( ) ;
86- let expressions = metadata. get_expression_columns ( ) ;
87- let found = metadata
88- . get_table_columns ( ) ?
89- . iter ( )
90- . chain ( expressions)
91- . contains ( s) ;
92- Some ( !found)
93- } )
94- . unwrap_or ( true ) ;
95-
96- valid. set ( title_is_valid) ;
97- new_value. set ( maybe_s. clone ( ) ) ;
98- on_change. emit ( ( maybe_s, title_is_valid) ) ;
99- } ,
100- ) ;
101-
102- {
103- clone ! ( value_state, new_value) ;
104- yew:: use_effect_with (
105- ( p. editable , p. initial_value . clone ( ) ) ,
106- move |( editable, value) | {
107- if !editable {
108- value_state. set ( ValueState :: Unedited ) ;
109- }
110- new_value. set ( value. to_owned ( ) ) ;
100+ fn update ( & mut self , ctx : & yew:: prelude:: Context < Self > , msg : Self :: Message ) -> bool {
101+ match msg {
102+ EditableHeaderMsg :: SetNewValue ( new_value) => {
103+ let maybe_value = ( !new_value. is_empty ( ) ) . then_some ( new_value. clone ( ) ) ;
104+ self . edited = ctx. props ( ) . initial_value != maybe_value;
105+
106+ self . valid = maybe ! ( {
107+ if maybe_value
108+ . as_ref( )
109+ . map( |v| v == & self . placeholder)
110+ . unwrap_or( true )
111+ {
112+ return Some ( true ) ;
113+ }
114+ if !self . edited {
115+ return Some ( true ) ;
116+ }
117+ let metadata = ctx. props( ) . session. metadata( ) ;
118+ let expressions = metadata. get_expression_columns( ) ;
119+ let found = metadata
120+ . get_table_columns( ) ?
121+ . iter( )
122+ . chain( expressions)
123+ . contains( & new_value) ;
124+ Some ( !found)
125+ } )
126+ . unwrap_or ( true ) ;
127+
128+ self . value = maybe_value. clone ( ) ;
129+ ctx. props ( ) . on_change . emit ( ( maybe_value, self . valid ) ) ;
130+
131+ tracing:: error!( "EditableHeader: SetNewValue! {:?}" , self ) ;
132+
133+ true
134+ } ,
135+ EditableHeaderMsg :: OnClick ( ( ) ) => {
136+ self . noderef
137+ . cast :: < HtmlInputElement > ( )
138+ . unwrap ( )
139+ . focus ( )
140+ . unwrap ( ) ;
141+ false
111142 } ,
112- ) ;
143+ }
113144 }
114145
115- let onclick = yew:: use_callback ( noderef. clone ( ) , |_, noderef| {
116- noderef. cast :: < HtmlInputElement > ( ) . unwrap ( ) . focus ( ) . unwrap ( ) ;
117- } ) ;
118- let onblur = yew:: use_callback (
119- set_new_value. clone ( ) ,
120- move |e : FocusEvent , set_new_value| {
146+ fn view ( & self , ctx : & yew:: prelude:: Context < Self > ) -> Html {
147+ let mut classes = classes ! ( "sidebar_header_contents" ) ;
148+ if ctx. props ( ) . editable {
149+ classes. push ( "editable" ) ;
150+ }
151+ if !self . valid {
152+ classes. push ( "invalid" ) ;
153+ }
154+ if self . edited {
155+ classes. push ( "edited" ) ;
156+ }
157+
158+ let onkeyup = ctx. link ( ) . callback ( |e : KeyboardEvent | {
121159 let value = e. target_unchecked_into :: < HtmlInputElement > ( ) . value ( ) ;
122- set_new_value. emit ( value) ;
123- } ,
124- ) ;
125- let onkeyup = yew:: use_callback (
126- set_new_value. clone ( ) ,
127- move |e : KeyboardEvent , set_new_value| {
160+ EditableHeaderMsg :: SetNewValue ( value)
161+ } ) ;
162+ let onblur = ctx. link ( ) . callback ( |e : FocusEvent | {
128163 let value = e. target_unchecked_into :: < HtmlInputElement > ( ) . value ( ) ;
129- set_new_value. emit ( value) ;
130- } ,
131- ) ;
132-
133- let mut classes = classes ! ( "sidebar_header_contents" ) ;
134- if p. editable {
135- classes. push ( "editable" ) ;
136- }
137- if !* valid {
138- classes. push ( "invalid" ) ;
139- }
140- match * value_state {
141- ValueState :: Unedited => { } ,
142- ValueState :: Edited => classes. push ( "edited" ) ,
143- }
144- let split = p
145- . placeholder
146- . split_once ( '\n' )
147- . map ( |( a, _) | a)
148- . unwrap_or ( & p. placeholder ) ;
149- let placeholder = match split. char_indices ( ) . nth ( 25 ) {
150- None => split. to_string ( ) ,
151- Some ( ( idx, _) ) => split[ ..idx] . to_owned ( ) ,
152- } ;
153-
154- html ! {
155- <div
156- class={ classes}
157- { onclick}
158- >
159- if let Some ( icon) = p. icon_type {
160- <TypeIcon ty={ icon} />
161- }
162- <input
163- ref={ noderef}
164- type ="search"
165- class="sidebar_header_title"
166- disabled={ !p. editable}
167- { onblur}
168- { onkeyup}
169- value={ ( * new_value) . clone( ) }
170- { placeholder}
171- />
172- </div>
164+ EditableHeaderMsg :: SetNewValue ( value)
165+ } ) ;
166+ html ! {
167+ <div
168+ class={ classes}
169+ onclick={ ctx. link( ) . callback( |_| EditableHeaderMsg :: OnClick ( ( ) ) ) }
170+ >
171+ if let Some ( icon) = ctx. props( ) . icon_type {
172+ <TypeIcon ty={ icon} />
173+ }
174+ <input
175+ ref={ self . noderef. clone( ) }
176+ type ="search"
177+ class="sidebar_header_title"
178+ disabled={ !ctx. props( ) . editable}
179+ { onblur}
180+ { onkeyup}
181+ value={ self . value. clone( ) }
182+ placeholder={ self . placeholder. clone( ) }
183+ />
184+ </div>
185+ }
173186 }
174187}
0 commit comments