@@ -4,11 +4,44 @@ use leptos::prelude::*;
44use nightshade_api:: editor:: protocol:: {
55 ClientMessage , CommandLogEntry , CommandLogKind , EditorAction ,
66} ;
7+ use nightshade_api:: prelude:: CommandSpec ;
78use serde_json:: { Value , json} ;
89
910use crate :: bridge:: { Bridge , act, send} ;
1011use crate :: state:: { EditorState , InspectorTab } ;
1112
13+ /// The command reference, keyed by name, from the one manifest the bindings are
14+ /// generated from, so the docs cannot drift from the commands.
15+ fn command_docs ( ) -> HashMap < String , CommandSpec > {
16+ nightshade_api:: prelude:: command_manifest ( )
17+ . into_iter ( )
18+ . map ( |spec| ( spec. name . to_string ( ) , spec) )
19+ . collect ( )
20+ }
21+
22+ /// A command's one-line signature: name, fields with their types, and reply.
23+ fn signature ( spec : & CommandSpec ) -> String {
24+ let fields = spec
25+ . fields
26+ . iter ( )
27+ . map ( |field| format ! ( "{}: {}" , field. name, field. type_name) )
28+ . collect :: < Vec < _ > > ( )
29+ . join ( ", " ) ;
30+ if spec. reply == "none" {
31+ format ! ( "{}({fields})" , spec. name)
32+ } else {
33+ format ! ( "{}({fields}) \u{2192} {}" , spec. name, spec. reply)
34+ }
35+ }
36+
37+ /// A command's field types, keyed by field name, for the per-field tooltips.
38+ fn field_types ( spec : & CommandSpec ) -> HashMap < String , String > {
39+ spec. fields
40+ . iter ( )
41+ . map ( |field| ( field. name . to_string ( ) , field. type_name . to_string ( ) ) )
42+ . collect ( )
43+ }
44+
1245/// Every command variant the api exposes, as (name, arguments schema), read
1346/// from the one source: `command_schema`. The builder lists all of them, so it
1447/// always matches the `Command` enum instead of a hand-picked subset.
@@ -150,6 +183,7 @@ fn defaults_for(args: &Value, selected: Option<u32>) -> HashMap<String, String>
150183fn field_input (
151184 name : String ,
152185 schema : Value ,
186+ type_name : String ,
153187 fields : RwSignal < HashMap < String , String > > ,
154188 state : EditorState ,
155189) -> AnyView {
@@ -171,7 +205,7 @@ fn field_input(
171205 } ;
172206 return view ! {
173207 <div class="console-field" >
174- <label class="console-field-label" >{ name. clone( ) } </label>
208+ <label class="console-field-label" title=type_name . clone ( ) >{ name. clone( ) } <span class= "console-field-type" > { type_name . clone ( ) } </span> </label>
175209 <div class="console-field-row" >
176210 <input
177211 class="console-input"
@@ -201,7 +235,7 @@ fn field_input(
201235 if let Some ( options) = enum_options ( & schema) {
202236 return view ! {
203237 <div class="console-field" >
204- <label class="console-field-label" >{ name. clone( ) } </label>
238+ <label class="console-field-label" title=type_name . clone ( ) >{ name. clone( ) } <span class= "console-field-type" > { type_name . clone ( ) } </span> </label>
205239 <select
206240 class="console-select"
207241 prop: value=read
@@ -220,7 +254,7 @@ fn field_input(
220254 if schema. get ( "type" ) . and_then ( Value :: as_str) == Some ( "boolean" ) {
221255 return view ! {
222256 <div class="console-field console-field-inline" >
223- <label class="console-field-label" >{ name. clone( ) } </label>
257+ <label class="console-field-label" title=type_name . clone ( ) >{ name. clone( ) } <span class= "console-field-type" > { type_name . clone ( ) } </span> </label>
224258 <input
225259 type ="checkbox"
226260 prop: checked=move || read( ) == "true"
@@ -242,7 +276,7 @@ fn field_input(
242276 . unwrap_or_default ( ) ;
243277 view ! {
244278 <div class="console-field" >
245- <label class="console-field-label" >{ name. clone( ) } </label>
279+ <label class="console-field-label" title=type_name . clone ( ) >{ name. clone( ) } <span class= "console-field-type" > { type_name . clone ( ) } </span> </label>
246280 <input
247281 class="console-input"
248282 r#type=if numeric { "number" } else { "text" }
@@ -263,6 +297,9 @@ pub fn Console(
263297 state : EditorState ,
264298) -> impl IntoView {
265299 let catalog = StoredValue :: new ( command_catalog ( ) ) ;
300+ let docs = StoredValue :: new ( command_docs ( ) ) ;
301+ let reference_open = RwSignal :: new ( false ) ;
302+ let reference_filter = RwSignal :: new ( String :: new ( ) ) ;
266303 let names: Vec < String > =
267304 catalog. with_value ( |c| c. iter ( ) . map ( |( name, _) | name. clone ( ) ) . collect ( ) ) ;
268305 let first = names. first ( ) . cloned ( ) . unwrap_or_default ( ) ;
@@ -328,10 +365,68 @@ pub fn Console(
328365 >
329366 <div class="console-header" >
330367 <span class="console-title" >"Commands and Events" </span>
331- <button class="console-clear" on: click=move |_| state. command_log. set( Vec :: new( ) ) >
332- "Clear"
333- </button>
368+ <div class="console-header-actions" >
369+ <button
370+ class=move || {
371+ if reference_open. get( ) {
372+ "console-clear console-clear-active"
373+ } else {
374+ "console-clear"
375+ }
376+ }
377+ title="Browse every command and its signature"
378+ on: click=move |_| reference_open. update( |open| * open = !* open)
379+ >
380+ "Reference"
381+ </button>
382+ <button
383+ class="console-clear"
384+ on: click=move |_| state. command_log. set( Vec :: new( ) )
385+ >
386+ "Clear"
387+ </button>
388+ </div>
334389 </div>
390+ <Show when=move || reference_open. get( ) fallback=|| ( ) >
391+ <div class="console-reference" >
392+ <input
393+ class="console-input"
394+ placeholder="filter commands"
395+ prop: value=move || reference_filter. get( )
396+ on: input=move |event| reference_filter. set( event_target_value( & event) )
397+ />
398+ <div class="console-reference-list" >
399+ { move || {
400+ let filter = reference_filter. get( ) . to_lowercase( ) ;
401+ docs. with_value( |map| {
402+ let mut specs: Vec <CommandSpec > = map
403+ . values( )
404+ . filter( |spec| spec. name. to_lowercase( ) . contains( & filter) )
405+ . cloned( )
406+ . collect( ) ;
407+ specs. sort_by_key( |spec| spec. name) ;
408+ specs
409+ . into_iter( )
410+ . map( |spec| {
411+ let name = spec. name. to_string( ) ;
412+ view! {
413+ <button
414+ class="console-reference-item"
415+ on: click=move |_| {
416+ choose( name. clone( ) ) ;
417+ reference_open. set( false ) ;
418+ }
419+ >
420+ { signature( & spec) }
421+ </button>
422+ }
423+ } )
424+ . collect_view( )
425+ } )
426+ } }
427+ </div>
428+ </div>
429+ </Show >
335430 <div class="console-log" >
336431 <Show when=move || state. command_log. with( |log| log. is_empty( ) ) fallback=|| ( ) >
337432 <div class="console-empty" >
@@ -394,13 +489,24 @@ pub fn Console(
394489 . map( |name| view! { <option value=name. clone( ) >{ name. clone( ) } </option> } )
395490 . collect_view( ) }
396491 </select>
492+ <div class="console-signature" >
493+ { move || {
494+ docs. with_value( |map| map. get( & selected. get( ) ) . map( signature) ) . unwrap_or_default( )
495+ } }
496+ </div>
397497 <div class="console-fields" >
398498 { move || {
399499 let name = selected. get( ) ;
500+ let types = docs
501+ . with_value( |map| map. get( & name) . map( field_types) )
502+ . unwrap_or_default( ) ;
400503 match args_for( & name) {
401504 Some ( args) => variant_fields( & args)
402505 . into_iter( )
403- . map( |( field, schema) | field_input( field, schema, fields, state) )
506+ . map( |( field, schema) | {
507+ let type_name = types. get( & field) . cloned( ) . unwrap_or_default( ) ;
508+ field_input( field, schema, type_name, fields, state)
509+ } )
404510 . collect_view( )
405511 . into_any( ) ,
406512 None => ( ) . into_any( ) ,
0 commit comments