Skip to content

Commit d4eb82d

Browse files
committed
some changes
1 parent c5f36d8 commit d4eb82d

5 files changed

Lines changed: 108 additions & 76 deletions

File tree

client/src/main/scala/org/scastie/client/ScastieBackend.scala

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import scala.concurrent.duration._
1515
import scala.scalajs.concurrent.JSExecutionContext.Implicits.queue
1616
import org.scastie.runtime.api.RuntimeError
1717
import org.scastie.client.components.ScaladexSearch
18-
import org.scastie.client.components.tabStrip.TabStrip.Tab
18+
import org.scastie.client.components.tabStrip.TabStrip.{Tab, TabStripState}
1919
import org.scastie.client.scalacli.ScalaCliUtils._
2020

2121
case class ScastieBackend(scastieId: UUID, serverUrl: Option[String], scope: BackendScope[Scastie, ScastieState]) {
@@ -145,8 +145,28 @@ case class ScastieBackend(scastieId: UUID, serverUrl: Option[String], scope: Bac
145145
val openFile: File => Callback =
146146
f => scope.modState(_.openFile(f))
147147

148-
val moveFile: (String, String) => Callback =
149-
(srcPath, dstPath) => scope.modState(_.moveFile(srcPath, dstPath))
148+
val moveFileOrFolder: (FileOrFolder, String) => Callback =
149+
(f, dstFolderPath) => {
150+
scope.modState(ss => {
151+
val newRoot = FileOrFolderUtils.move(ss.inputs.code, f.path, dstFolderPath)
152+
ss.setRootFolder(newRoot)
153+
}) >>
154+
scope.modState(ss => {
155+
def updateTab(tab: Tab): Tab = {
156+
if (tab.tabId == f.path)
157+
tab.copy(tabId = dstFolderPath + "/" + f.name)
158+
else tab
159+
}
160+
161+
def isInF(tab: Tab): Boolean =
162+
f.isFolder && FileOrFolderUtils.find(f.asInstanceOf[Folder], tab.tabId).nonEmpty
163+
164+
ss.copy(tabStripState = ss.tabStripState match {
165+
case TabStripState(selectedTab, activeTabs) =>
166+
TabStripState(selectedTab.filterNot(isInF).map(updateTab), activeTabs.filterNot(isInF).map(updateTab))
167+
})
168+
})
169+
}
150170

151171
val closeTab: Tab => Callback =
152172
tab => scope.modState(_.closeTab(tab))

client/src/main/scala/org/scastie/client/ScastieState.scala

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -311,11 +311,6 @@ case class ScastieState(
311311
}
312312
}
313313

314-
def moveFile(srcPath: String, dstPath: String): ScastieState = {
315-
val newCode = FileOrFolderUtils.move(inputs.code, srcPath, dstPath)
316-
setRootFolder(newCode)
317-
}
318-
319314
def setRootFolder(code: Folder): ScastieState = {
320315
if (inputs.code != code) {
321316
copyAndSave(

client/src/main/scala/org/scastie/client/components/Scastie.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ object Scastie {
9191
i(cls := s"fa fa-${if (state.isSidePaneOpen) "angle-left" else "angle-right"}")
9292
),
9393
div(cls := "side-pane-content")(
94-
FileHierarchy(state.inputs.code, scope.backend.openFile, scope.backend.moveFile).render
94+
FileHierarchy(state.inputs.code, scope.backend.openFile, scope.backend.moveFileOrFolder).render
9595
).when(state.isSidePaneOpen)
9696
).unless(props.isEmbedded || state.isPresentationMode),
9797
MainPanel(

client/src/main/scala/org/scastie/client/components/fileHierarchy/FileHierarchy.scala

Lines changed: 42 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,46 +5,50 @@ import japgolly.scalajs.react._
55
import japgolly.scalajs.react.vdom.html_<^._
66

77

8-
final case class FileHierarchy(rootFolder: Folder, openFile: File => Callback, moveFile: (String, String) => Callback) {
9-
@inline def render: VdomElement = FileHierarchy.component((rootFolder, openFile, moveFile))
8+
/**
9+
* @param rootFolder hierarchy to display
10+
* @param openFile when user clicks on a file in this hierarchy this function is called
11+
* @param moveFileOrFolder when user moves a file or folder this function is called, the string is the destination folder path
12+
*/
13+
final case class FileHierarchy(rootFolder: Folder, openFile: File => Callback, moveFileOrFolder: (FileOrFolder, String) => Callback) {
14+
@inline def render: VdomElement = FileHierarchy.component((rootFolder, openFile, moveFileOrFolder))
1015
}
1116

1217
object FileHierarchy {
1318

14-
case class FileHierarchyState(selectedFile: String, dragSrc: String, dragOver: String)
15-
16-
val initialState = FileHierarchyState(
17-
selectedFile = "",
18-
dragSrc = "",
19-
dragOver = "")
20-
21-
val component =
22-
ScalaFnComponent.withHooks[(Folder, File => Callback, (String, String) => Callback)]
23-
.useState(initialState)
24-
.render((props, fhs) => {
25-
val rootFolder = props._1
26-
val openFile = props._2
27-
val moveFileCb = props._3
28-
29-
val selectFile: File => Callback = {
30-
f => openFile(f)
31-
}
32-
val dragInfoUpdate: DragInfo => Callback = {
33-
di =>
34-
if (di.start && !di.end) {
35-
fhs.modState(_.copy(dragSrc = di.f.path))
36-
} else if (!di.start && di.end) {
37-
val srcPath = fhs.value.dragSrc
38-
val dstPath = fhs.value.dragOver
39-
moveFileCb(srcPath, dstPath)
40-
} else if (!di.start && !di.end) {
41-
fhs.modState(_.copy(dragOver = di.f.path))
42-
} else {
43-
Callback.throwException(new IllegalArgumentException())
44-
}
45-
}
46-
<.div(
47-
FileOrFolderNode(rootFolder, fhs.value.selectedFile, 0, selectFile, dragInfoUpdate).render
48-
)
49-
})
19+
/**
20+
* @param selectedFile currently selected file in the hierarchy view
21+
* @param dragOver FileOrFolder's path that the user could drop to
22+
*/
23+
private case class FileHierarchyState(selectedFile: String, dragOver: String)
24+
25+
private val initialFhs = FileHierarchyState(selectedFile = "/root", dragOver = "")
26+
27+
private val component = ScalaFnComponent.withHooks[(Folder, File => Callback, (FileOrFolder, String) => Callback)]
28+
.useState(initialFhs)
29+
.render((props, fhs) => {
30+
val rootFolder: Folder = props._1
31+
val openFile: File => Callback = props._2
32+
val moveFileOrFolder: (FileOrFolder, String) => Callback = props._3
33+
34+
val selectFile: File => Callback = {
35+
(f: File) => openFile(f) >> fhs.modState(_.copy(selectedFile = f.path))
36+
}
37+
38+
val dragInfoUpdate: DragInfo => Callback = {
39+
dragInfo =>
40+
if (dragInfo.end) {
41+
val src = dragInfo.fileOrFolder
42+
val dstPath = fhs.value.dragOver
43+
44+
moveFileOrFolder(src, dstPath) >>
45+
fhs.setState(FileHierarchyState(selectedFile = dstPath + "/" + src.name, dragOver = ""))
46+
} else {
47+
fhs.modState(_.copy(dragOver = dragInfo.fileOrFolder.path))
48+
}
49+
}
50+
<.div(
51+
FileOrFolderNode(rootFolder, fhs.value.selectedFile, 0, selectFile, dragInfoUpdate).render
52+
)
53+
})
5054
}

client/src/main/scala/org/scastie/client/components/fileHierarchy/FileOrFolderNode.scala

Lines changed: 42 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,80 +2,94 @@ package org.scastie.client.components.fileHierarchy
22

33
import org.scastie.api._
44
import japgolly.scalajs.react._
5-
import japgolly.scalajs.react.vdom.all.cls
5+
import japgolly.scalajs.react.hooks.Hooks.UseState
66
import japgolly.scalajs.react.vdom.html_<^._
77

8-
case class DragInfo(start: Boolean, end: Boolean, f: FileOrFolder)
9-
10-
final case class FileOrFolderNode(file: FileOrFolder, selectedFile: String, depth: Int, selectFile: File => Callback, dragStartOrEnd: DragInfo => Callback) {
11-
12-
@inline def render: VdomElement = FileOrFolderNode.component((file, selectedFile, depth, selectFile, dragStartOrEnd))
8+
/**
9+
* Provides information about drag over or end.
10+
*
11+
* @param end true if drag ended
12+
* @param fileOrFolder file or folder that is being dragged (end is true) or dragged over (end is false)
13+
*/
14+
case class DragInfo(end: Boolean, fileOrFolder: FileOrFolder)
15+
16+
/**
17+
* Node in the file hierarchy view that represents a file or a folder.
18+
* In case of a folder we can collapse or expand the children.
19+
* It can be dragged around and dropped on another folder.
20+
*
21+
* @param fileOrFolder node that we display
22+
* @param selectedFile selected file's path in the view hierarchy
23+
* @param depth depth of the node in the hierarchy from root (used to shift the node to the right)
24+
* @param selectFile callback when user clicks on a file
25+
* @param dragOverOrEnd callback when user drags over a folder or ends dragging a file or folder
26+
*/
27+
final case class FileOrFolderNode(fileOrFolder: FileOrFolder, selectedFile: String, depth: Int, selectFile: File => Callback, dragOverOrEnd: DragInfo => Callback) {
28+
29+
@inline def render: VdomElement = FileOrFolderNode.component((fileOrFolder, selectedFile, depth, selectFile, dragOverOrEnd))
1330
}
1431

1532
object FileOrFolderNode {
1633

17-
1834
val component = ScalaFnComponent.withHooks[(FileOrFolder, String, Int, File => Callback, DragInfo => Callback)]
1935

2036
.useState(true) //isExpanded
2137
.useState(false) //isMouseOver
2238

23-
.render((props, isExpanded, isMouseOver) => {
24-
val (file, s, depth, selectFile, dragStartOrEnd) = props
39+
.render((props, isExpanded: UseState[Boolean], isMouseOver: UseState[Boolean]) => {
40+
val (fileOrFolder, s, depth, selectFile, dragOverOrEnd) = props
2541

26-
val icon = file match {
42+
val icon = fileOrFolder match {
43+
case _: File => "file-o"
2744
case _: Folder if isExpanded.value => "folder-open"
2845
case _: Folder => "folder"
29-
case _: File => "file-o"
3046
}
3147

3248
val handleClick = (e: ReactMouseEvent) => {
33-
e.stopPropagationCB >> (file match {
34-
case _: Folder => isExpanded.modState(x => !x)
35-
case f: File => selectFile(f)
36-
})
37-
}
38-
39-
val onDragStart = (e: ReactDragEvent) => {
40-
dragStartOrEnd(DragInfo(start = true, end = false, file))
49+
e.stopPropagation()
50+
fileOrFolder match {
51+
case f: File => selectFile(f)
52+
case _: Folder => isExpanded.modState(x => !x)
53+
}
4154
}
4255

4356
val onDragOver = (e: ReactDragEvent) => {
44-
isMouseOver.setState(true).when(file.isFolder) >>
45-
dragStartOrEnd(DragInfo(start = false, end = false, file))
57+
if (fileOrFolder.isFolder) {
58+
isMouseOver.setState(true) >>
59+
dragOverOrEnd(DragInfo(end = false, fileOrFolder))
60+
} else Callback.empty
4661
}
4762

4863
val onDragEnd = (e: ReactDragEvent) => {
49-
dragStartOrEnd(DragInfo(start = false, end = true, file))
64+
dragOverOrEnd(DragInfo(end = true, fileOrFolder))
5065
}
5166

5267
<.div(
5368
<.div(
5469
^.cls := s"hierarchy-list-row",
55-
^.cls := s"${if (file.path.equals(s)) " file-selected" else ""}",
70+
^.cls := s"${if (fileOrFolder.path.equals(s)) " file-selected" else ""}",
5671
^.cls := s"${if (isMouseOver.value) "file-mouse-over" else ""}",
5772
^.onClick ==> handleClick,
5873
^.draggable := true,
59-
^.onDragStart ==> onDragStart,
6074
^.onDragEnd ==> onDragEnd,
6175
^.onDragOver ==> onDragOver,
6276
^.onDragLeave --> isMouseOver.setState(false),
6377
^.onMouseOver --> isMouseOver.setState(true),
6478
^.onMouseLeave --> isMouseOver.setState(false),
65-
^.key := file.path,
79+
^.key := fileOrFolder.path,
6680
<.div(
6781
^.paddingLeft := s"${16 * depth}px",
6882
<.i(^.className := s"fa fa-${icon}"),
69-
file.name
83+
fileOrFolder.name
7084
)
7185
),
7286

7387
<.div(
7488
if (isExpanded.value) {
75-
file match {
89+
fileOrFolder match {
7690
case folder: Folder =>
7791
folder.children.map {
78-
f: FileOrFolder => FileOrFolderNode(f, s, depth + 1, selectFile, dragStartOrEnd).render
92+
f: FileOrFolder => FileOrFolderNode(f, s, depth + 1, selectFile, dragOverOrEnd).render
7993
}.toVdomArray
8094
case _: File => EmptyVdom
8195
}
@@ -84,6 +98,5 @@ object FileOrFolderNode {
8498
}
8599
)
86100
)
87-
88101
})
89102
}

0 commit comments

Comments
 (0)