@@ -21,6 +21,8 @@ import NeoTextInput from "./component/NeoTextInput";
2121import TextInput from "react-materialize/lib/TextInput" ;
2222import Card from "react-materialize/lib/Card" ;
2323import NeoTextButton from "./component/NeoTextButton" ;
24+ import NeoShareModal from "./component/NeoShareModal" ;
25+ import { JSONCrush } from "jsoncrush" ;
2426
2527
2628/**
@@ -31,6 +33,8 @@ import NeoTextButton from "./component/NeoTextButton";
3133 * - Loading/storing dashboards as JSON (optionally from the browser cache)
3234 * - The creation, ordering and deleting of the NeoCard components.
3335 * - Propagating global parameter changes ("Selection" reports) to each of the cards.
36+ *
37+ * TODO: in many places we're modifying state directly. This is not a good practise, and should be refactored.
3438 */
3539class NeoDash extends React . Component {
3640 version = '1.1' ;
@@ -49,11 +53,13 @@ class NeoDash extends React.Component {
4953 // Attempt to load an existing dashboard state from the browser cache.
5054 this . loadDashboardfromBrowserCache ( ) ;
5155
56+
5257 if ( window . neo4jDesktopApi ) {
5358 // Set the connection details from the Neo4j Desktop integration API.
5459 this . setConnectionDetailsFromDesktopIntegration ( ) ;
5560 } else {
5661 // check the browser cache or use default connection values.
62+
5763 this . setConnectionDetailsFromBrowserCache ( ) ;
5864 }
5965
@@ -83,8 +89,23 @@ class NeoDash extends React.Component {
8389 encryption : ( localStorage . getItem ( 'neodash-encryption' ) ) ? localStorage . getItem ( 'neodash-encryption' ) : 'off' ,
8490 }
8591
86- this . createConnectionModal ( this . connect , true ) ;
87- this . stateChanged ( { label : "HideError" } )
92+ let urlParams = new URLSearchParams ( window . location . search ) ;
93+ if ( urlParams . get ( "url" ) !== null ) {
94+ this . state . jsonToLoad = urlParams . get ( "url" ) ;
95+ try {
96+ this . state . connectionToLoad = JSON . parse ( atob ( urlParams . get ( "connection" ) ) ) ;
97+ } catch {
98+ // Unable to parse encoded JSON, don't set a connection
99+ this . state . connectionToLoad = null ;
100+ }
101+ this . createExternalDashboardLoadPopupModal ( ) ;
102+ this . stateChanged ( { } )
103+ // String representations of the data to load, as encoded in hte URL.
104+
105+ } else {
106+ this . createConnectionModal ( this . connect , true ) ;
107+ this . stateChanged ( { label : "HideError" } )
108+ }
88109 }
89110
90111 /**
@@ -120,9 +141,10 @@ class NeoDash extends React.Component {
120141
121142 removeDuplicateConnectionModal ( ) {
122143 var select = document . getElementById ( 'root' ) ;
123- if ( select . childNodes . length == 9 ) {
124- select . removeChild ( select . childNodes . item ( 1 ) ) ;
144+ if ( select . childNodes . length == 11 ) {
145+ select . removeChild ( select . childNodes . item ( 2 ) ) ;
125146 }
147+
126148 }
127149
128150 /**
@@ -158,6 +180,13 @@ class NeoDash extends React.Component {
158180 localStorage . setItem ( 'neodash-password' , this . connection . password . toString ( ) ) ;
159181 localStorage . setItem ( 'neodash-encryption' , this . connection . encryption ) ;
160182
183+ if ( this . confirmation ) {
184+ this . confirmation = false ;
185+ this . stateChanged ( {
186+ label : "CreateError" ,
187+ value : "Dashboard loaded! You are connected to " + this . connection . url + "."
188+ } )
189+ }
161190
162191 } )
163192 . catch ( error => {
@@ -174,7 +203,7 @@ class NeoDash extends React.Component {
174203 } finally {
175204
176205 }
177- if ( ! this . connected ) {
206+ if ( ! this . connected ) {
178207 this . createConnectionModal ( this . connect , true ) ;
179208 // TODO - this can produce duplicate connection modals
180209 }
@@ -200,11 +229,7 @@ class NeoDash extends React.Component {
200229 }
201230 // If a JSON string is available, try to parse it and set the state.
202231 try {
203- let loaded = JSON . parse ( this . state . json )
204- // Quietly auto-upgrade to Neodash 1.1...
205- if ( this . version === "1.1" && loaded . version === "1.0" ) {
206- this . upgradeDashboardJson ( loaded ) ;
207- }
232+ let loaded = this . parseJson ( this . state . json ) ;
208233 if ( loaded . version && loaded . version !== this . version ) {
209234 this . stateChanged ( {
210235 label : "CreateError" ,
@@ -227,6 +252,15 @@ class NeoDash extends React.Component {
227252 }
228253 }
229254
255+ parseJson ( text ) {
256+ let loaded = JSON . parse ( text ) ;
257+ // Quietly auto-upgrade to Neodash 1.1...
258+ if ( this . version === "1.1" && loaded . version === "1.0" ) {
259+ this . upgradeDashboardJson ( loaded ) ;
260+ }
261+ return loaded ;
262+ }
263+
230264 upgradeDashboardJson ( loaded ) {
231265 loaded . version = "1.1" ;
232266 loaded . pages = [
@@ -324,6 +358,7 @@ class NeoDash extends React.Component {
324358 * @param update - a JSON dictionary {update, label} describing the change that was made.
325359 */
326360 stateChanged ( update ) {
361+ console . log ( update . label )
327362 if ( update . label === "ConnectURLChanged" ) {
328363 this . connection . url = update . value ;
329364 }
@@ -346,6 +381,45 @@ class NeoDash extends React.Component {
346381 this . buildJSONFromReportsState ( ) ;
347382
348383 }
384+
385+ if ( update . label === "LoadExternalDashboard" ) {
386+ fetch ( this . state . jsonToLoad )
387+ . then ( response => response . text ( ) )
388+ . then ( ( data ) => {
389+ // this.stateChanged({label: "HideError"})
390+ // this.createExternalDashboardLoadPopupModal()
391+ let parsedJson = this . parseJson ( data ) ;
392+ this . state . json = data ;
393+ // this.buildJSONFromReportsState();
394+ // console.log(this.state.connectionToLoad)
395+ if ( this . state . connectionToLoad ) {
396+ this . connection = this . state . connectionToLoad ;
397+ if ( this . connection . password ) {
398+ this . confirmation = true ;
399+ this . connect ( ) ;
400+ return ;
401+ }
402+ }
403+
404+ this . stateChanged ( {
405+ label : "CreateError" ,
406+ value : "Dashboard loaded! You can now connect to Neo4j."
407+ } )
408+ this . stateChanged ( { label : "OpenConnectionModal" } )
409+ var select = document . getElementById ( 'root' ) ;
410+ select . removeChild ( select . childNodes . item ( 9 ) ) ;
411+
412+
413+ } ) . catch ( error => {
414+ this . createConnectionModal ( this . connect , true )
415+ this . stateChanged ( { label : "CreateError" , value : error . toString ( ) } )
416+
417+ } )
418+ }
419+ if ( update . label === "OpenConnectionModal" ) {
420+ this . createConnectionModal ( this . connect , true )
421+ }
422+
349423 if ( update . label === "AskForDeletePage" ) {
350424 this . createPageDeletionPopupModal ( ) ;
351425 this . state . count += 1 ;
@@ -429,6 +503,27 @@ class NeoDash extends React.Component {
429503 if ( update . label !== "SaveModalUpdated" ) {
430504 this . buildJSONFromReportsState ( ) ;
431505 }
506+ if ( update . label === "ShareLinkURLChanged" ) {
507+ this . state . shareURL = encodeURIComponent ( update . value ) ;
508+ this . createShareURLConnectionDetails ( ) ;
509+ }
510+
511+ if ( update . label === "ShareLinkCredentialsChanged" ) {
512+ this . saveCredentialsInShareLink = ! this . saveCredentialsInShareLink ;
513+ this . createShareURLConnectionDetails ( ) ;
514+ if ( ! this . saveCredentialsInShareLink ) {
515+ this . state . shareURLConnectionDetails = null ;
516+ }
517+ }
518+ if ( update . label === "ShareLinkPasswordChanged" ) {
519+ this . savePasswordInShareLink = ! this . savePasswordInShareLink ;
520+ this . createShareURLConnectionDetails ( ) ;
521+
522+ }
523+
524+ if ( update . label === "ShareLinkGenerated" ) {
525+ this . createShareURLConnectionDetails ( ) ;
526+ }
432527 this . setState ( this . state ) ;
433528 }
434529
@@ -579,6 +674,53 @@ class NeoDash extends React.Component {
579674 ] } />
580675 }
581676
677+ /**
678+ * Creates a pop-up window (Modal). Used for displaying errors and other notifications.
679+ */
680+ createExternalDashboardLoadPopupModal ( ) {
681+ let header = "NeoDash - Loading Dashboard" ;
682+ let displayURL = ( this . state . jsonToLoad . length > 100 ) ?
683+ this . state . jsonToLoad . substring ( 0 , 100 ) + "..." : this . state . jsonToLoad ;
684+
685+ let content = < div >
686+ < p > You are loading a dashboard from: </ p >
687+ < b > < a href = { this . state . jsonToLoad } target = { "_blank" } > { displayURL } </ a > </ b >
688+ { ( this . state . connectionToLoad ) ?
689+ < p > You will be connected to < b > { this . state . connectionToLoad . url } </ b > .</ p > : < > </ >
690+ }
691+ < p > This will overwrite your current dashboard (if present). Continue?</ p >
692+ </ div >
693+
694+
695+ // Create the modal object
696+ this . errorModal = < NeoModal header = { header }
697+ open = { true }
698+ trigger = { null }
699+ content = { content }
700+ key = { this . state . count }
701+ id = { this . state . count }
702+ root = { document . getElementById ( "root" ) }
703+ actions = { [
704+ < Button flat modal = "close"
705+ node = "button"
706+ onClick = { e => this . stateChanged ( { label : "OpenConnectionModal" } ) }
707+ waves = "red" > Cancel</ Button > ,
708+ < NeoTextButton right modal = "close"
709+ color = { "white-color" }
710+ icon = 'play_arrow'
711+ node = "button"
712+ modal = "close"
713+ style = { { backgroundColor : "green" } }
714+ onClick = { e => {
715+ this . stateChanged ( { label : "HideError" } )
716+ this . stateChanged ( { label : "LoadExternalDashboard" } )
717+ }
718+ }
719+ text = { "load" }
720+ waves = "green" />
721+ ] } />
722+ }
723+
582724 /**
583725 * Creates a pop-up window (Modal). Used for displaying errors and other notifications.
584726 */
@@ -610,10 +752,14 @@ class NeoDash extends React.Component {
610752 */
611753 handleSpecialCaseErrors ( content , header ) {
612754 var style = { }
755+
613756 // Special case 1: we're connecting to a database from Neo4j Desktop.
614757 if ( content . startsWith ( "Trying to connect" ) ) {
615758 header = "Connecting..." ;
616759 }
760+ if ( content . startsWith ( "Dashboard loaded!" ) ) {
761+ header = "Dashboard loaded 🎉" ;
762+ }
617763 if ( content . startsWith ( "To save a dashboard" ) ) {
618764 header = "Saving and Loading Dashboards" ;
619765 style = { paddingBottom : "650px" }
@@ -626,7 +772,7 @@ class NeoDash extends React.Component {
626772 content = "Unable to connect to the specified Neo4j database. " +
627773 "The database might be unreachable, or it does not accept " +
628774 ( ( encryption === "on" ) ? "encrypted" : "unencrypted" ) + " connections. " + content ;
629- this . state . page += 1 ;
775+ this . state . page += 1 ;
630776
631777 }
632778 // Special case 3: we're dealing with someone clicking the 'Get in touch' button.
@@ -690,11 +836,12 @@ class NeoDash extends React.Component {
690836 * Create a modal (pop-up) that's used for saving/loading/exporting dashboards as JSON.
691837 */
692838 createSaveLoadModal ( loadJson ) {
693- let trigger = < NavItem href = "" onClick = { e => this . stateChanged ( { } ) } > Load/Export </ NavItem > ;
839+ let trigger = < NavItem href = "" onClick = { e => this . stateChanged ( { } ) } > Save/Load </ NavItem > ;
694840 return < NeoSaveLoadModal json = { this . state . json }
695841 loadJson = { loadJson }
696842 trigger = { trigger }
697843 onQuestionMarkClicked = { this . onConnectionHelpClicked }
844+ onCancel = { e => this . stateChanged ( { } ) }
698845 value = { this . state . json }
699846 placeholder = { this . props . placeholder }
700847 change = { e => {
@@ -708,6 +855,45 @@ class NeoDash extends React.Component {
708855 /> ;
709856 }
710857
858+ /**
859+ * Create a modal (pop-up) that's used for saving/loading/exporting dashboards as JSON.
860+ */
861+ createShareModal ( loadJson ) {
862+ let trigger = < NavItem href = "#" > Share</ NavItem > ;
863+ return < NeoShareModal json = { this . props . json }
864+ loadJson = { loadJson }
865+ trigger = { trigger }
866+ connection = { this . saveCredentialsInShareLink }
867+ password = { this . savePasswordInShareLink }
868+ onQuestionMarkClicked = { this . onConnectionHelpClicked }
869+ onCancel = { e => this . stateChanged ( { } ) }
870+ value = { this . state . shareURL }
871+ connectionValue = { this . state . shareURLConnectionDetails }
872+ placeholder = { this . props . placeholder }
873+ change = { e => {
874+ this . state . json = e . target . value ;
875+ this . setState ( this . state )
876+ } }
877+ stateChanged = { this . stateChanged }
878+ /> ;
879+ }
880+
881+
882+ createShareURLConnectionDetails ( ) {
883+ if ( this . saveCredentialsInShareLink ) {
884+ let password = ( this . savePasswordInShareLink ) ? this . connection . password : "" ;
885+ let connection =
886+ {
887+ url : this . connection . url ,
888+ username : this . connection . username ,
889+ password : password ,
890+ database : this . connection . database ,
891+ encryption : this . connection . encryption
892+ }
893+ this . state . shareURLConnectionDetails = btoa ( JSON . stringify ( connection ) ) ;
894+ }
895+ }
896+
711897 /**
712898 *
713899 * @param connect - method used for opening the connection.
@@ -720,7 +906,7 @@ class NeoDash extends React.Component {
720906 connect = { connect }
721907 connection = { this . connection }
722908 stateChanged = { this . stateChanged }
723- navClicked = { e => this . stateChanged ( { } ) }
909+ nfavClicked = { e => this . stateChanged ( { } ) }
724910 onConnect = { this . onConnectClicked ( connect ) }
725911 onGetInTouchClicked = { this . onGetInTouchClicked ( ) }
726912 />
@@ -730,7 +916,7 @@ class NeoDash extends React.Component {
730916 /**
731917 * Creates the navigation bar of the dashboard.
732918 */
733- createDashboardNavbar ( saveLoadModal ) {
919+ createDashboardNavbar ( saveLoadModal , shareModal ) {
734920 let dashboardTitle = < Textarea disabled = { ! this . state . editable } noLayout = { true }
735921 className = "card-title editable-title"
736922 key = { this . state . count }
@@ -806,7 +992,7 @@ class NeoDash extends React.Component {
806992 tabs
807993 }
808994 style = { { backgroundColor : '#111' } } >
809- { saveLoadModal }
995+ { saveLoadModal } { shareModal }
810996 { ( this . neoConnectionModal ) ? this . neoConnectionModal : < div > </ div >
811997 }
812998 </ Navbar > ;
@@ -874,8 +1060,9 @@ class NeoDash extends React.Component {
8741060 */
8751061 render ( ) {
8761062 let saveLoadModal = this . createSaveLoadModal ( this . createCardObjectsFromDashboardState ) ;
1063+ let shareModal = this . createShareModal ( this . createCardObjectsFromDashboardState ) ;
8771064 let cardsContainer = this . createCardsContainer ( )
878- let navbar = this . createDashboardNavbar ( saveLoadModal ) ;
1065+ let navbar = this . createDashboardNavbar ( saveLoadModal , shareModal ) ;
8791066 let errorModal = ( this . errorModal ) ? this . errorModal : "" ;
8801067 var select = document . getElementById ( 'root' ) ;
8811068 return (
0 commit comments