1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
module Gargantext.Components.Nodes.Corpus where
import Data.Array as A
import Data.Either (Either(..))
import Data.Eq.Generic (genericEq)
import Data.Generic.Rep (class Generic)
import Data.List as List
import Data.Maybe (Maybe(..), fromMaybe)
import Data.Show.Generic (genericShow)
import Effect (Effect)
import Effect.Aff (throwError)
import Effect.Exception (error)
import Gargantext.Components.App.Data (Boxes)
import Gargantext.Components.CodeEditor as CE
import Gargantext.Components.FolderView as FV
import Gargantext.Components.InputWithEnter (inputWithEnter)
import Gargantext.Components.Node (NodePoly(..), HyperdataList)
import Gargantext.Components.Nodes.Corpus.Types (CorpusData, Hyperdata)
import Gargantext.Components.Nodes.Types (FTField, FTFieldWithIndex, FTFieldsWithIndex(..), Field(..), FieldType(..), Hash, Index, defaultHaskell', defaultJSON', defaultMarkdown', defaultPython')
import Gargantext.Components.TileMenu (tileMenu)
import Gargantext.Config.REST (RESTError(..), AffRESTError)
import Gargantext.Data.Array as GDA
import Gargantext.Prelude (class Eq, class Show, Unit, bind, discard, pure, show, unit, ($), (<>), const, (<<<), (+), (==), (-), (<), (>), (<$>))
import Gargantext.Routes (SessionRoute(Children, NodeAPI))
import Gargantext.Routes as GR
import Gargantext.Sessions (Session, get, put, sessionId)
import Gargantext.Types (AffETableResult, NodeType(..), ID)
import Gargantext.Utils.Crypto as Crypto
import Gargantext.Utils.Reactix as R2
import Gargantext.Utils.Toestand as T2
import Reactix as R
import Reactix.DOM.HTML as H
import Simple.JSON as JSON
import Toestand as T
here :: R2.Here
here = R2.here "Gargantext.Components.Nodes.Corpus"
type Props =
( boxes :: Boxes
, nodeId :: ID
, session :: Session )
corpusLayout :: R2.Leaf Props
corpusLayout props = R.createElement corpusLayoutCpt props []
corpusLayoutCpt :: R.Component Props
corpusLayoutCpt = here.component "corpusLayout" cpt where
cpt { boxes, nodeId, session } _ = do
pure $ corpusLayoutMain { boxes, key, nodeId, session }
where
key = show (sessionId session) <> "-" <> show nodeId
type KeyProps =
( boxes :: Boxes
, key :: String
, nodeId :: ID
, session :: Session
)
corpusLayoutMain :: R2.Leaf KeyProps
corpusLayoutMain props = R.createElement corpusLayoutMainCpt props []
corpusLayoutMainCpt :: R.Component KeyProps
corpusLayoutMainCpt = here.component "corpusLayoutMain" cpt
where
cpt { boxes, nodeId, session } _ = do
-- Computed
corpusCodeRoute <- pure $ const do
pure $ GR.CorpusCode (sessionId session) nodeId
-- Render
pure $
H.div {}
[
tileMenu
{ boxes
, currentTile: Just corpusCodeRoute
, xTile: Just corpusCodeRoute
, yTile: Just corpusCodeRoute
}
[
H.button
{ className: "btn btn-primary" }
[
H.i { className: "fa fa-code" } []
]
]
,
H.hr {}
,
FV.folderView
{ backFolder: true
, boxes
, nodeId
, session
}
]
-----------------------------------
type FieldsCodeEditorProps =
(
fields :: T.Box FTFieldsWithIndex
| LoadProps
)
fieldsCodeEditor :: R2.Component FieldsCodeEditorProps
fieldsCodeEditor = R.createElement fieldsCodeEditorCpt
fieldsCodeEditorCpt :: R.Component FieldsCodeEditorProps
fieldsCodeEditorCpt = here.component "fieldsCodeEditorCpt" cpt
where
cpt { fields } _ = do
(FTFieldsWithIndex fields') <- T.useLive T.unequal fields
masterKey <- T.useBox T2.newReload
masterKey' <- T.useLive T.unequal masterKey
let editorsMap { idx, ftField } =
fieldCodeEditorWrapper { canMoveDown: idx < (List.length fields' - 1)
, canMoveUp: idx > 0
, field: ftField
, key: (show masterKey') <> "-" <> (show idx)
, onChange: onChange idx
, onMoveDown: onMoveDown masterKey idx
, onMoveUp: onMoveUp masterKey idx
, onRemove: onRemove idx
, onRename: onRename idx
}
pure $ H.div {} $ List.toUnfoldable (editorsMap <$> fields')
where
onChange :: Index -> FieldType -> Effect Unit
onChange idx typ = do
T.modify_ (\(FTFieldsWithIndex fs) ->
FTFieldsWithIndex $ fromMaybe fs $
List.modifyAt idx (\{ ftField: Field f} -> { idx, ftField: Field $ f { typ = typ } }) fs) fields
onMoveDown :: T2.ReloadS -> Index -> Unit -> Effect Unit
onMoveDown masterKey idx _ = do
T2.reload masterKey
T.modify_ (\(FTFieldsWithIndex fs) -> recomputeIndices $ FTFieldsWithIndex $ GDA.swapList idx (idx + 1) fs) fields
onMoveUp :: T2.ReloadS -> Index -> Unit -> Effect Unit
onMoveUp masterKey idx _ = do
T2.reload masterKey
T.modify_ (\(FTFieldsWithIndex fs) -> recomputeIndices $ FTFieldsWithIndex $ GDA.swapList idx (idx - 1) fs) fields
onRemove :: Index -> Unit -> Effect Unit
onRemove idx _ = do
T.modify_ (\(FTFieldsWithIndex fs) -> FTFieldsWithIndex $ fromMaybe fs $ List.deleteAt idx fs) fields
onRename :: Index -> String -> Effect Unit
onRename idx newName = do
T.modify_ (\(FTFieldsWithIndex fs) ->
FTFieldsWithIndex $ fromMaybe fs $
List.modifyAt idx (\{ ftField: Field f } -> { idx, ftField: Field $ f { name = newName } }) fs) fields
recomputeIndices :: FTFieldsWithIndex -> FTFieldsWithIndex
recomputeIndices (FTFieldsWithIndex lst) = FTFieldsWithIndex $ List.mapWithIndex (\idx -> \{ ftField } -> { idx, ftField }) lst
hash :: FTFieldWithIndex -> Hash
hash { idx, ftField } = Crypto.hash $ "--idx--" <> (show idx) <> "--field--" <> (show ftField)
type FieldCodeEditorProps =
(
canMoveDown :: Boolean
, canMoveUp :: Boolean
, field :: FTField
, key :: String
, onChange :: FieldType -> Effect Unit
, onMoveDown :: Unit -> Effect Unit
, onMoveUp :: Unit -> Effect Unit
, onRemove :: Unit -> Effect Unit
, onRename :: String -> Effect Unit
)
fieldCodeEditorWrapper :: Record FieldCodeEditorProps -> R.Element
fieldCodeEditorWrapper props = R.createElement fieldCodeEditorWrapperCpt props []
fieldCodeEditorWrapperCpt :: R.Component FieldCodeEditorProps
fieldCodeEditorWrapperCpt = here.component "fieldCodeEditorWrapperCpt" cpt
where
cpt props@{canMoveDown, canMoveUp, field: Field { name }, onMoveDown, onMoveUp, onRemove, onRename} _ = do
pure $ H.div { className: "card mb-3" } [
H.div { className: "card-header" } [
H.div { className: "code-editor-heading row no-gutters justify-content-between" } [
H.div { className: "col-5" } [
inputWithEnter { onBlur: onRename
, onEnter: \_ -> pure unit
, onValueChanged: onRename
, autoFocus: false
, className: "form-control"
, defaultValue: name
, placeholder: "Enter file name"
, type: "text" }
]
, H.div { className: "d-flex flex-column" } ([
H.div { className: "btn btn-danger mb-1"
, on: { click: \_ -> onRemove unit }
} [
H.span { className: "fa fa-trash" } [ ]
]
] <> moveButtons)
]
]
, H.div { className: "card-body" } [
fieldCodeEditor props
]
]
where
moveButtons = [] <> (if canMoveDown then [moveDownButton] else [])
<> (if canMoveUp then [moveUpButton] else [])
moveDownButton =
H.div { className: "btn btn-primary"
, on: { click: \_ -> onMoveDown unit }
} [
H.span { className: "fa fa-arrow-down" } [ ]
]
moveUpButton =
H.div { className: "btn btn-primary"
, on: { click: \_ -> onMoveUp unit }
} [
H.span { className: "fa fa-arrow-up" } [ ]
]
type RenameableProps =
(
onRename :: String -> Effect Unit
, text :: String
)
renameable :: Record RenameableProps -> R.Element
renameable props = R.createElement renameableCpt props []
renameableCpt :: R.Component RenameableProps
renameableCpt = here.component "renameableCpt" cpt
where
cpt {onRename, text} _ = do
isEditing <- T.useBox false
state <- T.useBox text
textRef <- R.useRef text
-- handle props change of text
R.useEffect1' text $ do
if R.readRef textRef == text then
pure unit
else do
R.setRef textRef text
T.write_ text state
pure $ H.div { className: "renameable" } [
renameableText { isEditing, onRename, state }
]
type RenameableTextProps =
(
isEditing :: T.Box Boolean
, onRename :: String -> Effect Unit
, state :: T.Box String
)
renameableText :: Record RenameableTextProps -> R.Element
renameableText props = R.createElement renameableTextCpt props []
renameableTextCpt :: R.Component RenameableTextProps
renameableTextCpt = here.component "renameableTextCpt" cpt
where
cpt { isEditing, onRename, state } _ = do
isEditing' <- T.useLive T.unequal isEditing
state' <- T.useLive T.unequal state
pure $ if isEditing' then
H.div { className: "input-group" }
[ inputWithEnter {
autoFocus: false
, className: "form-control text"
, defaultValue: state'
, onBlur: \st -> T.write_ st state
, onEnter: submit state'
, onValueChanged: \st -> T.write_ st state
, placeholder: ""
, type: "text"
}
, H.div { className: "btn input-group-append"
, on: { click: submit state' } }
[ H.span { className: "fa fa-floppy-o" } []
]
]
else
H.div { className: "input-group" }
[ H.input { className: "form-control"
, defaultValue: state'
, disabled: 1
, type: "text" }
, H.div { className: "btn input-group-append"
, on: { click: \_ -> T.write_ true isEditing } }
[ H.span { className: "fa fa-pencil" } []
]
]
where
submit text _ = do
T.write_ false isEditing
onRename text
fieldCodeEditor :: Record FieldCodeEditorProps -> R.Element
fieldCodeEditor props = R.createElement fieldCodeEditorCpt props []
fieldCodeEditorCpt :: R.Component FieldCodeEditorProps
fieldCodeEditorCpt = here.component "fieldCodeEditorCpt" cpt
where
cpt {field: Field {typ: typ@(Haskell {haskell})}, onChange} _ = do
pure $ CE.codeEditor {code: haskell, defaultCodeType: CE.Haskell, onChange: changeCode onChange typ}
cpt {field: Field {typ: typ@(Python {python})}, onChange} _ = do
pure $ CE.codeEditor {code: python, defaultCodeType: CE.Python, onChange: changeCode onChange typ}
cpt {field: Field {typ: typ@(JSON j)}, onChange} _ = do
pure $ CE.codeEditor {code, defaultCodeType: CE.JSON, onChange: changeCode onChange typ}
where
code = R2.stringify (JSON.writeImpl j) 2
cpt {field: Field {typ: typ@(Markdown {text})}, onChange} _ = do
pure $ CE.codeEditor {code: text, defaultCodeType: CE.Markdown, onChange: changeCode onChange typ}
-- Performs the matrix of code type changes
-- (FieldType -> Effect Unit) is the callback function for fields array
-- FieldType is the current element that we will modify
-- CE.CodeType is the editor code type (might have been the cause of the trigger)
-- CE.Code is the editor code (might have been the cause of the trigger)
changeCode :: (FieldType -> Effect Unit) -> FieldType -> CE.CodeType -> CE.Code -> Effect Unit
changeCode onc (Haskell hs) CE.Haskell c = onc $ Haskell $ hs { haskell = c }
changeCode onc (Haskell _) CE.Python c = onc $ Python $ defaultPython' { python = c }
changeCode onc (Haskell {haskell}) CE.JSON _ = onc $ JSON $ defaultJSON' { desc = haskell }
changeCode onc (Haskell {haskell}) CE.Markdown _ = onc $ Markdown $ defaultMarkdown' { text = haskell }
changeCode onc (Python hs) CE.Python c = onc $ Python $ hs { python = c }
changeCode onc (Python _) CE.Haskell c = onc $ Haskell $ defaultHaskell' { haskell = c }
changeCode onc (Python {python}) CE.JSON _ = onc $ JSON $ defaultJSON' { desc = python }
changeCode onc (Python {python}) CE.Markdown _ = onc $ Markdown $ defaultMarkdown' { text = python }
changeCode onc (Markdown _) CE.Haskell c = onc $ Haskell $ defaultHaskell' { haskell = c }
changeCode onc (Markdown _) CE.Python c = onc $ Python $ defaultPython' { python = c }
changeCode onc (Markdown _) CE.JSON c = onc $ Markdown $ defaultMarkdown' { text = c }
changeCode onc (Markdown md) CE.Markdown c = onc $ Markdown $ md { text = c }
changeCode onc (JSON j) CE.Haskell _ = onc $ Haskell $ defaultHaskell' { haskell = haskell }
where
haskell = R2.stringify (JSON.writeImpl j) 2
changeCode onc (JSON j) CE.Python _ = onc $ Python $ defaultPython' { python = toCode }
where
toCode = R2.stringify (JSON.writeImpl j) 2
changeCode onc _ CE.JSON c = do
case JSON.readJSON c of
Left err -> here.log2 "[fieldCodeEditor'] cannot parse json" c -- TODO Refactor?
Right j' -> onc $ JSON j'
-- case jsonParser c of
-- Left err -> here.log2 "[fieldCodeEditor'] cannot parse json" c
-- Right j' -> case decodeJson j' of
-- Left err -> here.log2 "[fieldCodeEditor'] cannot decode json" j'
-- Right j'' -> onc $ JSON j''
changeCode onc (JSON j) CE.Markdown _ = onc $ Markdown $ defaultMarkdown' { text = text }
where
text = R2.stringify (JSON.writeImpl j) 2
type LoadProps =
( nodeId :: Int
, session :: Session
)
loadCorpus' :: Record LoadProps -> AffRESTError (NodePoly Hyperdata)
loadCorpus' {nodeId, session} = get session $ NodeAPI Corpus (Just nodeId) ""
-- Just to make reloading effective
loadCorpusWithReload :: { reload :: T2.Reload | LoadProps } -> AffRESTError (NodePoly Hyperdata)
loadCorpusWithReload {nodeId, session} = loadCorpus' {nodeId, session}
type SaveProps = (
hyperdata :: Hyperdata
| LoadProps
)
saveCorpus :: Record SaveProps -> AffRESTError Int
saveCorpus {hyperdata, nodeId, session} = do
put session (NodeAPI Corpus (Just nodeId) "") hyperdata
loadCorpus :: Record LoadProps -> AffRESTError CorpusData
loadCorpus {nodeId, session} = do
-- fetch corpus via lists parentId
res <- get session nodePolyRoute
case res of
Left err -> pure $ Left err
Right (NodePoly {parentId: corpusId} :: NodePoly {}) -> do
eCorpusNode <- get session $ corpusNodeRoute corpusId ""
eDefaultListIds <- (get session $ defaultListIdsRoute corpusId)
:: forall a. JSON.ReadForeign a => AffETableResult (NodePoly a)
case eCorpusNode of
Left err -> pure $ Left err
Right corpusNode -> do
case eDefaultListIds of
Left err -> pure $ Left err
Right defaultListIds -> do
case (A.head defaultListIds.docs :: Maybe (NodePoly HyperdataList)) of
Just (NodePoly { id: defaultListId }) ->
pure $ Right { corpusId, corpusNode, defaultListId }
Nothing ->
pure $ Left $ CustomError "Missing default list"
-- (NodePoly {parentId: corpusId} :: NodePoly {}) <- get session nodePolyRoute
-- corpusNode <- get session $ corpusNodeRoute corpusId ""
-- defaultListIds <- (get session $ defaultListIdsRoute corpusId)
-- :: forall a. JSON.ReadForeign a => AffTableResult (NodePoly a)
-- case (A.head defaultListIds.docs :: Maybe (NodePoly HyperdataList)) of
-- Just (NodePoly { id: defaultListId }) ->
-- pure {corpusId, corpusNode, defaultListId}
-- Nothing ->
-- throwError $ error "Missing default list"
where
nodePolyRoute = NodeAPI Corpus (Just nodeId) ""
corpusNodeRoute = NodeAPI Corpus <<< Just
defaultListIdsRoute = Children NodeList 0 1 Nothing <<< Just
loadCorpusWithChild :: Record LoadProps -> AffRESTError CorpusData
loadCorpusWithChild { nodeId: childId, session } = do
-- fetch corpus via lists parentId
eListNode <- get session $ listNodeRoute childId ""
case eListNode of
Left err -> pure $ Left err
Right listNode -> do
let (NodePoly {parentId: corpusId} :: NodePoly {}) = listNode
eCorpusNode <- get session $ corpusNodeRoute corpusId ""
case eCorpusNode of
Left err -> pure $ Left err
Right corpusNode -> do
eDefaultListIds <- (get session $ defaultListIdsRoute corpusId)
:: forall a. JSON.ReadForeign a => AffETableResult (NodePoly a)
case eDefaultListIds of
Left err -> pure $ Left err
Right defaultListIds -> do
case (A.head defaultListIds.docs :: Maybe (NodePoly HyperdataList)) of
Just (NodePoly { id: defaultListId }) ->
pure $ Right { corpusId, corpusNode, defaultListId }
Nothing ->
throwError $ error "Missing default list"
where
corpusNodeRoute = NodeAPI Corpus <<< Just
listNodeRoute = NodeAPI Node <<< Just
defaultListIdsRoute = Children NodeList 0 1 Nothing <<< Just
type LoadWithReloadProps =
(
reload :: T2.Reload
| LoadProps
)
-- Just to make reloading effective
loadCorpusWithChildAndReload :: Record LoadWithReloadProps -> AffRESTError CorpusData
loadCorpusWithChildAndReload {nodeId, session} = loadCorpusWithChild {nodeId, session}
data ViewType = Code | Folders
derive instance Generic ViewType _
instance Eq ViewType where
eq = genericEq
instance Show ViewType where
show = genericShow
type ViewTypeSelectorProps =
(
state :: T.Box ViewType
)
viewTypeSelector :: Record ViewTypeSelectorProps -> R.Element
viewTypeSelector p = R.createElement viewTypeSelectorCpt p []
viewTypeSelectorCpt :: R.Component ViewTypeSelectorProps
viewTypeSelectorCpt = here.component "viewTypeSelector" cpt
where
cpt {state} _ = do
state' <- T.useLive T.unequal state
pure $ H.div { className: "btn-group"
, role: "group" } [
viewTypeButton Folders state' state
, viewTypeButton Code state' state
]
viewTypeButton viewType state' state =
H.button { className: "btn btn-primary" <> active
, on: { click: \_ -> T.write viewType state }
, type: "button"
} [
H.i { className: "fa " <> (icon viewType) } []
]
where
active = if viewType == state' then " active" else ""
icon Folders = "fa-folder"
icon Code = "fa-code"