1+ import { gql } from "@apollo/client" ;
2+ import { AsNodeL1Input , ChildGroup , CullNodePhrasingToBeEmbedded , GetMap , GetNode , GetNodeChildrenL2 , GetNodeDisplayText , GetNodeL2 , NodeL1 , NodeL3 , NodeLink , NodePhrasing , NodeRevision , NodeType , Polarity } from "dm_common" ;
3+ import { E , ModifyString } from "js-vextensions" ;
4+ import { CreateAccessor , GetAsync } from "mobx-graphlink" ;
5+ import React from "react" ;
6+ import { Button , CheckBox , Column , Row , Text } from "react-vcomponents" ;
7+ import { BaseComponent } from "react-vextensions" ;
8+ import { ShowMessageBox } from "react-vmessagebox" ;
9+ import { store } from "Store" ;
10+ import { GetOpenMapID } from "Store/main.js" ;
11+ import { ImportResource , IR_NodeAndRevision } from "Utils/DataFormats/DataExchangeFormat.js" ;
12+ import { RunCommand_AddChildNode } from "Utils/DB/Command.js" ;
13+ import { apolloClient } from "Utils/LibIntegrations/Apollo.js" ;
14+ import { AddNotificationMessage , ES , Observer , RunInAction_Set } from "web-vcore" ;
15+ import { CreateAncestorForResource , CreateResource , ResolveNodeIDsForInsertPath } from "./Utils.js" ;
16+
17+ @Observer
18+ export class ImportResourceUI extends BaseComponent <
19+ {
20+ importUnderNode : NodeL3 , resource : ImportResource , index : number , resources : ImportResource [ ] ,
21+ autoSearchByTitle : boolean ,
22+ searchQueryGen : number , onNodeCreated : ( ) => any ,
23+ } ,
24+ { search : boolean , existingNodesWithTitle : number | n }
25+ > {
26+ ComponentWillMountOrReceiveProps ( props , forMount ) {
27+ if ( forMount || props . autoSearchByTitle != this . props . autoSearchByTitle ) {
28+ this . SetState ( { search : props . autoSearchByTitle } , ( ) => {
29+ this . ApplySearchSetting ( ) ;
30+ } ) ;
31+ }
32+ // here, we only rerun the search (based on search-query-generation), if we haven't found a matching node yet
33+ else if ( props . searchQueryGen != this . props . searchQueryGen && ( this . state . existingNodesWithTitle ?? 0 ) == 0 ) {
34+ this . ApplySearchSetting ( ) ;
35+ }
36+ }
37+ async ApplySearchSetting ( ) {
38+ const res = this . props . resource ;
39+ if ( this . state . search && res instanceof IR_NodeAndRevision && res . CanSearchByTitle ( ) ) {
40+ // todo: update this to work against new rust backend! (atm this query fails, hence ui option for it disabled)
41+ const result = await apolloClient . query ( {
42+ query : gql `
43+ query SearchQueryForImport($title: String!) {
44+ nodeRevisions(filter: {phrasing: {contains: {text_base: $title}}}) {
45+ nodes { id }
46+ }
47+ }
48+ ` ,
49+ variables : { title : res . revision . phrasing . text_base } ,
50+ fetchPolicy : "network-only" ,
51+ } ) ;
52+ const foundNodeIDs = result . data . nodeRevisions . nodes . map ( a => a . id ) ;
53+ this . SetState ( { existingNodesWithTitle : foundNodeIDs . length } ) ;
54+ } else {
55+ this . SetState ( { existingNodesWithTitle : null } ) ;
56+ }
57+ }
58+
59+ render ( ) {
60+ const { importUnderNode, resource : res , index, resources, onNodeCreated} = this . props ;
61+ const { search, existingNodesWithTitle} = this . state ;
62+ const uiState = store . main . maps . importSubtreeDialog ;
63+ const pathStr = res . pathInData . join ( "." ) ; //+ (resource.path.length > 0 ? "." : "");
64+
65+ const map = GetMap ( GetOpenMapID ( ) ) ;
66+
67+ const insertPath = res instanceof IR_NodeAndRevision && res . insertPath_titles ? res . insertPath_titles : [ ] ;
68+ const insertPath_resolvedNodeIDs = ResolveNodeIDsForInsertPath ( importUnderNode . id , insertPath ) ;
69+ const parentNodeID = insertPath . length > 0 ? insertPath_resolvedNodeIDs . LastOrX ( ) : importUnderNode . id ;
70+ const ownNodeTextResolved = parentNodeID != null && res instanceof IR_NodeAndRevision && res . ownTitle != null
71+ // use CatchBail, so that after each node-add, it doesn't cause the rows to switch to "Loading..." (which causes loss of the scroll-position)
72+ ? ResolveNodeIDsForInsertPath . CatchBail ( [ null ] , parentNodeID , [ res . ownTitle ] ) . Last ( ) != null
73+ : false ;
74+
75+ return (
76+ < Column mt = { index == 0 ? 0 : 5 } pr = { 5 } sel style = { { border : "solid gray" , borderWidth : index == 0 ? 0 : "1px 0 0 0" } } >
77+ < Row >
78+ { res instanceof IR_NodeAndRevision &&
79+ < >
80+ < Text style = { { flexShrink : 0 , fontWeight : "bold" , padding : "0 3px" , background : "rgba(128,128,128,.5)" , marginBottom : - 5 } } > { pathStr } </ Text >
81+ < Row ml = { 5 } mr = { 5 } style = { { flex : 1 , display : "block" } } >
82+ < Text mr = { 3 } style = { { display : "inline-block" , flexShrink : 0 , fontWeight : "bold" } } >
83+ { ModifyString ( res . node . type , m => [ m . startLower_to_upper ] ) }
84+ { res . node . type == NodeType . argument && res . link . polarity != null &&
85+ < Text style = { { display : "inline-block" , flexShrink : 0 , fontWeight : "bold" } } > [{ res . link . polarity == Polarity . supporting ? "pro" : "con" } ]</ Text > }
86+ { ":" }
87+ </ Text >
88+ < Column >
89+ < Row > { res . revision . phrasing . text_base } </ Row >
90+ { ( res . revision . phrasing . text_question ?? "" ) . length > 0 && < Row > { `<question form> ${ res . revision . phrasing . text_question } ` } </ Row > }
91+ { ( res . revision . phrasing . text_narrative ?? "" ) . length > 0 && < Row > { `<narrative form> ${ res . revision . phrasing . text_narrative } ` } </ Row > }
92+ </ Column >
93+ </ Row >
94+ </ > }
95+ < Column >
96+ { res instanceof IR_NodeAndRevision && res . CanSearchByTitle ( ) &&
97+ < CheckBox ml = { 5 } text = { `Search: ${ existingNodesWithTitle ?? "?" } ` }
98+ style = { ES (
99+ { flex : 1 } ,
100+ existingNodesWithTitle == 0 && { background : "rgba(0,255,0,.5)" } ,
101+ existingNodesWithTitle != null && existingNodesWithTitle > 0 && { background : "rgba(255,0,0,.5)" } ,
102+ ) }
103+
104+ // temp-disabled, till backend supports the search feature
105+ enabled = { false }
106+ title = "This feature is currently disabled, until the backend is updated to support title-based node[-revision] searching."
107+
108+ value = { search } onChange = { async val => {
109+ this . SetState ( { search : val } , ( ) => {
110+ this . ApplySearchSetting ( ) ;
111+ } ) ;
112+ } } /> }
113+ < CheckBox ml = { 5 } text = "Selected"
114+ style = { E (
115+ { flexShrink : 0 } ,
116+ uiState . selectedImportResources . has ( res ) && { background : "rgba(255,0,255,.5)" } ,
117+ ) }
118+ value = { uiState . selectedImportResources . has ( res ) }
119+ onChange = { ( val , e ) => {
120+ const ev = e . nativeEvent as MouseEvent ;
121+ RunInAction_Set ( this , ( ) => {
122+ const newSelected = val ;
123+ let startI = index ;
124+ let lastI = index ;
125+ // select range, if holding down ctrl-key (on windows), command-key (on mac), or shift-key (on either -- though must click *exactly* on the checkbox, not the label)
126+ if ( ev . ctrlKey || ev . metaKey || ev . shiftKey ) {
127+ if ( uiState . selectFromIndex != - 1 ) {
128+ startI = Math . min ( uiState . selectFromIndex , index ) ;
129+ lastI = Math . max ( uiState . selectFromIndex , index ) ;
130+ }
131+ } else {
132+ uiState . selectFromIndex = index ;
133+ }
134+
135+ for ( let i = startI ; i <= lastI ; i ++ ) {
136+ if ( newSelected ) {
137+ uiState . selectedImportResources . add ( resources [ i ] ) ;
138+ } else {
139+ uiState . selectedImportResources . delete ( resources [ i ] ) ;
140+ }
141+ }
142+ } ) ;
143+ } } />
144+ </ Column >
145+ </ Row >
146+ { uiState . showAutoInsertTools &&
147+ < Row sel style = { { background : "rgba(0,0,0,.3)" , padding : 3 } } >
148+ < Text > Path:</ Text >
149+ { insertPath . map ( ( segment , segmentIndex ) => {
150+ const prevResolvedNodeID = segmentIndex == 0 ? importUnderNode . id : insertPath_resolvedNodeIDs [ segmentIndex - 1 ] ;
151+ const prevResolvedNode = GetNodeL2 ( prevResolvedNodeID ) ;
152+ const resolvedNodeID = insertPath_resolvedNodeIDs [ segmentIndex ] ;
153+ return (
154+ < Row center key = { segmentIndex }
155+ style = { E (
156+ { marginLeft : 5 , padding : "0 3px" , borderRadius : 5 , cursor : "pointer" } ,
157+ ! resolvedNodeID && { background : "rgba(255,0,0,.5)" } ,
158+ resolvedNodeID && { background : "rgba(0,255,0,.5)" } ,
159+ ) }
160+ onClick = { ( ) => {
161+ if ( prevResolvedNodeID && ! resolvedNodeID && res instanceof IR_NodeAndRevision ) {
162+ ShowMessageBox ( {
163+ cancelButton : true ,
164+ title : "Create this category node?" ,
165+ message : `
166+ Parent:${ prevResolvedNode ? GetNodeDisplayText ( prevResolvedNode , null , map ) : "n/a" } (id: ${ prevResolvedNodeID } )
167+ NewNode:${ segment }
168+ ` . AsMultiline ( 0 ) ,
169+ onOK : async ( ) => {
170+ const success = await CreateAncestorForResource ( res , map ?. id , prevResolvedNodeID , segment , res . node . accessPolicy ) ;
171+ if ( success ) {
172+ //await command.RunOnServer();
173+ onNodeCreated ( ) ;
174+ } else {
175+ AddNotificationMessage ( `Could not create ancestor "${ segment } ".` ) ;
176+ }
177+ } ,
178+ } ) ;
179+ }
180+ } }
181+ >
182+ { segment }
183+ </ Row >
184+ ) ;
185+ } ) }
186+ < Row ml = "auto" >
187+ { res instanceof IR_NodeAndRevision &&
188+ < >
189+ < Button text = { ownNodeTextResolved ? "Create (again)" : "Create" } p = "0 10px" enabled = { insertPath_resolvedNodeIDs . length == 0 || insertPath_resolvedNodeIDs . LastOrX ( ) != null }
190+ style = { E (
191+ ownNodeTextResolved && { backgroundColor : "rgba(0,255,0,.5)" } ,
192+ ) }
193+ onClick = { async ( ) => {
194+ await CreateResource ( res , map ?. id , insertPath_resolvedNodeIDs . LastOrX ( ) ?? importUnderNode . id ) ;
195+ onNodeCreated ( ) ;
196+ } } />
197+ </ > }
198+ </ Row >
199+ </ Row > }
200+ </ Column >
201+ ) ;
202+ }
203+ }
0 commit comments