@@ -34,6 +34,10 @@ import {
3434} from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest" ;
3535import WidgetStore from "../../../stores/WidgetStore" ;
3636import { UPDATE_EVENT } from "../../../stores/AsyncStore" ;
37+ import { SETTINGS } from "../../../settings/Settings" ;
38+ import SettingsStore , { LEVEL_ORDER } from "../../../settings/SettingsStore" ;
39+ import Modal from "../../../Modal" ;
40+ import ErrorDialog from "./ErrorDialog" ;
3741
3842class GenericEditor extends React . PureComponent {
3943 // static propTypes = {onBack: PropTypes.func.isRequired};
@@ -794,6 +798,286 @@ class WidgetExplorer extends React.Component {
794798 }
795799}
796800
801+ class SettingsExplorer extends React . Component {
802+ static getLabel ( ) {
803+ return _t ( "Settings Explorer" ) ;
804+ }
805+
806+ constructor ( props ) {
807+ super ( props ) ;
808+
809+ this . state = {
810+ query : '' ,
811+ editSetting : null , // set to a setting ID when editing
812+ viewSetting : null , // set to a setting ID when exploring in detail
813+
814+ explicitValues : null , // stringified JSON for edit view
815+ explicitRoomValues : null , // stringified JSON for edit view
816+ } ;
817+ }
818+
819+ onQueryChange = ( ev ) => {
820+ this . setState ( { query : ev . target . value } ) ;
821+ } ;
822+
823+ onExplValuesEdit = ( ev ) => {
824+ this . setState ( { explicitValues : ev . target . value } ) ;
825+ } ;
826+
827+ onExplRoomValuesEdit = ( ev ) => {
828+ this . setState ( { explicitRoomValues : ev . target . value } ) ;
829+ } ;
830+
831+ onBack = ( ) => {
832+ if ( this . state . editSetting ) {
833+ this . setState ( { editSetting : null } ) ;
834+ } else if ( this . state . viewSetting ) {
835+ this . setState ( { viewSetting : null } ) ;
836+ } else {
837+ this . props . onBack ( ) ;
838+ }
839+ } ;
840+
841+ onViewClick = ( ev , settingId ) => {
842+ ev . preventDefault ( ) ;
843+ this . setState ( { viewSetting : settingId } ) ;
844+ } ;
845+
846+ onEditClick = ( ev , settingId ) => {
847+ ev . preventDefault ( ) ;
848+ this . setState ( {
849+ editSetting : settingId ,
850+ explicitValues : this . renderExplicitSettingValues ( settingId , null ) ,
851+ explicitRoomValues : this . renderExplicitSettingValues ( settingId , this . props . room . roomId ) ,
852+ } ) ;
853+ } ;
854+
855+ onSaveClick = async ( ) => {
856+ try {
857+ const settingId = this . state . editSetting ;
858+ const parsedExplicit = JSON . parse ( this . state . explicitValues ) ;
859+ const parsedExplicitRoom = JSON . parse ( this . state . explicitRoomValues ) ;
860+ for ( const level of Object . keys ( parsedExplicit ) ) {
861+ console . log ( `[Devtools] Setting value of ${ settingId } at ${ level } from user input` ) ;
862+ try {
863+ const val = parsedExplicit [ level ] ;
864+ await SettingsStore . setValue ( settingId , null , level , val ) ;
865+ } catch ( e ) {
866+ console . warn ( e ) ;
867+ }
868+ }
869+ const roomId = this . props . room . roomId ;
870+ for ( const level of Object . keys ( parsedExplicit ) ) {
871+ console . log ( `[Devtools] Setting value of ${ settingId } at ${ level } in ${ roomId } from user input` ) ;
872+ try {
873+ const val = parsedExplicitRoom [ level ] ;
874+ await SettingsStore . setValue ( settingId , roomId , level , val ) ;
875+ } catch ( e ) {
876+ console . warn ( e ) ;
877+ }
878+ }
879+ this . setState ( {
880+ viewSetting : settingId ,
881+ editSetting : null ,
882+ } ) ;
883+ } catch ( e ) {
884+ Modal . createTrackedDialog ( 'Devtools - Failed to save settings' , '' , ErrorDialog , {
885+ title : _t ( "Failed to save settings" ) ,
886+ description : e . message ,
887+ } ) ;
888+ }
889+ } ;
890+
891+ renderSettingValue ( val ) {
892+ // Note: we don't .toString() a string because we want JSON.stringify to inject quotes for us
893+ const toStringTypes = [ 'boolean' , 'number' ] ;
894+ if ( toStringTypes . includes ( typeof ( val ) ) ) {
895+ return val . toString ( ) ;
896+ } else {
897+ return JSON . stringify ( val ) ;
898+ }
899+ }
900+
901+ renderExplicitSettingValues ( setting , roomId ) {
902+ const vals = { } ;
903+ for ( const level of LEVEL_ORDER ) {
904+ try {
905+ vals [ level ] = SettingsStore . getValueAt ( level , setting , roomId , true , true ) ;
906+ if ( vals [ level ] === undefined ) {
907+ vals [ level ] = null ;
908+ }
909+ } catch ( e ) {
910+ console . warn ( e ) ;
911+ }
912+ }
913+ return JSON . stringify ( vals , null , 4 ) ;
914+ }
915+
916+ renderCanEditLevel ( roomId , level ) {
917+ let canEdit = SettingsStore . canSetValue ( this . state . editSetting , roomId , level ) ;
918+ const className = canEdit ? 'mx_DevTools_SettingsExplorer_mutable' : 'mx_DevTools_SettingsExplorer_immutable' ;
919+ return < td className = { className } > < code > { canEdit . toString ( ) } </ code > </ td > ;
920+ }
921+
922+ render ( ) {
923+ const room = this . props . room ;
924+
925+ if ( ! this . state . viewSetting && ! this . state . editSetting ) {
926+ // view all settings
927+ const allSettings = Object . keys ( SETTINGS )
928+ . filter ( n => this . state . query ? n . toLowerCase ( ) . includes ( this . state . query . toLowerCase ( ) ) : true ) ;
929+ return (
930+ < div >
931+ < div className = "mx_Dialog_content mx_DevTools_SettingsExplorer" >
932+ < Field
933+ label = { _t ( 'Filter results' ) } autoFocus = { true } size = { 64 }
934+ type = "text" autoComplete = "off" value = { this . state . query } onChange = { this . onQueryChange }
935+ className = "mx_TextInputDialog_input mx_DevTools_RoomStateExplorer_query"
936+ />
937+ < table >
938+ < thead >
939+ < tr >
940+ < th > { _t ( "Setting ID" ) } </ th >
941+ < th > { _t ( "Value" ) } </ th >
942+ < th > { _t ( "Value in this room" ) } </ th >
943+ </ tr >
944+ </ thead >
945+ < tbody >
946+ { allSettings . map ( i => (
947+ < tr key = { i } >
948+ < td >
949+ < a href = "" onClick = { ( e ) => this . onViewClick ( e , i ) } >
950+ < code > { i } </ code >
951+ </ a >
952+ < a href = "" onClick = { ( e ) => this . onEditClick ( e , i ) }
953+ className = 'mx_DevTools_SettingsExplorer_edit'
954+ >
955+ ✏
956+ </ a >
957+ </ td >
958+ < td >
959+ < code > { this . renderSettingValue ( SettingsStore . getValue ( i ) ) } </ code >
960+ </ td >
961+ < td >
962+ < code >
963+ { this . renderSettingValue ( SettingsStore . getValue ( i , room . roomId ) ) }
964+ </ code >
965+ </ td >
966+ </ tr >
967+ ) ) }
968+ </ tbody >
969+ </ table >
970+ </ div >
971+ < div className = "mx_Dialog_buttons" >
972+ < button onClick = { this . onBack } > { _t ( "Back" ) } </ button >
973+ </ div >
974+ </ div >
975+ ) ;
976+ } else if ( this . state . editSetting ) {
977+ return (
978+ < div >
979+ < div className = "mx_Dialog_content mx_DevTools_SettingsExplorer" >
980+ < h3 > { _t ( "Setting:" ) } < code > { this . state . editSetting } </ code > </ h3 >
981+
982+ < div className = 'mx_DevTools_SettingsExplorer_warning' >
983+ < b > { _t ( "Caution:" ) } </ b > { _t (
984+ "This UI does NOT check the types of the values. Use at your own risk." ,
985+ ) }
986+ </ div >
987+
988+ < div >
989+ { _t ( "Setting definition:" ) }
990+ < pre > < code > { JSON . stringify ( SETTINGS [ this . state . editSetting ] , null , 4 ) } </ code > </ pre >
991+ </ div >
992+
993+ < div >
994+ < table >
995+ < thead >
996+ < tr >
997+ < th > { _t ( "Level" ) } </ th >
998+ < th > { _t ( "Settable at global" ) } </ th >
999+ < th > { _t ( "Settable at room" ) } </ th >
1000+ </ tr >
1001+ </ thead >
1002+ < tbody >
1003+ { LEVEL_ORDER . map ( lvl => (
1004+ < tr key = { lvl } >
1005+ < td > < code > { lvl } </ code > </ td >
1006+ { this . renderCanEditLevel ( null , lvl ) }
1007+ { this . renderCanEditLevel ( room . roomId , lvl ) }
1008+ </ tr >
1009+ ) ) }
1010+ </ tbody >
1011+ </ table >
1012+ </ div >
1013+
1014+ < div >
1015+ < Field
1016+ id = "valExpl" label = { _t ( "Values at explicit levels" ) } type = "text"
1017+ className = "mx_DevTools_textarea" element = "textarea"
1018+ autoComplete = "off" value = { this . state . explicitValues }
1019+ onChange = { this . onExplValuesEdit }
1020+ />
1021+ </ div >
1022+
1023+ < div >
1024+ < Field
1025+ id = "valExpl" label = { _t ( "Values at explicit levels in this room" ) } type = "text"
1026+ className = "mx_DevTools_textarea" element = "textarea"
1027+ autoComplete = "off" value = { this . state . explicitRoomValues }
1028+ onChange = { this . onExplRoomValuesEdit }
1029+ />
1030+ </ div >
1031+
1032+ </ div >
1033+ < div className = "mx_Dialog_buttons" >
1034+ < button onClick = { this . onSaveClick } > { _t ( "Save setting values" ) } </ button >
1035+ < button onClick = { this . onBack } > { _t ( "Back" ) } </ button >
1036+ </ div >
1037+ </ div >
1038+ ) ;
1039+ } else if ( this . state . viewSetting ) {
1040+ return (
1041+ < div >
1042+ < div className = "mx_Dialog_content mx_DevTools_SettingsExplorer" >
1043+ < h3 > { _t ( "Setting:" ) } < code > { this . state . viewSetting } </ code > </ h3 >
1044+
1045+ < div >
1046+ { _t ( "Setting definition:" ) }
1047+ < pre > < code > { JSON . stringify ( SETTINGS [ this . state . viewSetting ] , null , 4 ) } </ code > </ pre >
1048+ </ div >
1049+
1050+ < div >
1051+ { _t ( "Value:" ) }
1052+ < code > { this . renderSettingValue ( SettingsStore . getValue ( this . state . viewSetting ) ) } </ code >
1053+ </ div >
1054+
1055+ < div >
1056+ { _t ( "Value in this room:" ) }
1057+ < code > { this . renderSettingValue ( SettingsStore . getValue ( this . state . viewSetting , room . roomId ) ) } </ code >
1058+ </ div >
1059+
1060+ < div >
1061+ { _t ( "Values at explicit levels:" ) }
1062+ < pre > < code > { this . renderExplicitSettingValues ( this . state . viewSetting , null ) } </ code > </ pre >
1063+ </ div >
1064+
1065+ < div >
1066+ { _t ( "Values at explicit levels in this room:" ) }
1067+ < pre > < code > { this . renderExplicitSettingValues ( this . state . viewSetting , room . roomId ) } </ code > </ pre >
1068+ </ div >
1069+
1070+ </ div >
1071+ < div className = "mx_Dialog_buttons" >
1072+ < button onClick = { ( e ) => this . onEditClick ( e , this . state . viewSetting ) } > { _t ( "Edit Values" ) } </ button >
1073+ < button onClick = { this . onBack } > { _t ( "Back" ) } </ button >
1074+ </ div >
1075+ </ div >
1076+ ) ;
1077+ }
1078+ }
1079+ }
1080+
7971081const Entries = [
7981082 SendCustomEvent ,
7991083 RoomStateExplorer ,
@@ -802,6 +1086,7 @@ const Entries = [
8021086 ServersInRoomList ,
8031087 VerificationExplorer ,
8041088 WidgetExplorer ,
1089+ SettingsExplorer ,
8051090] ;
8061091
8071092export default class DevtoolsDialog extends React . PureComponent {
0 commit comments