Skip to content

Commit 00dbce6

Browse files
committed
Added a Data Provider to manage images
1 parent 9e1864a commit 00dbce6

File tree

5 files changed

+91
-7
lines changed

5 files changed

+91
-7
lines changed

spra-play-server/src/main/resources/application.conf

+10
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,16 @@ dataExplorer {
3030
}
3131
referenceDisplayField = "email"
3232
}
33+
34+
images {
35+
tableName = "images"
36+
primaryKeyField = "image_id"
37+
nonEditableColumns = ["image_id", "created_at"]
38+
canBeDeleted = false
39+
createFilter {
40+
requiredColumns = ["name", "data"]
41+
}
42+
}
3343
}
3444
}
3545

spra-web/src/main/scala/net/wiringbits/spra/ui/web/AdminView.scala

+2-3
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,12 @@ package net.wiringbits.spra.ui.web
33
import net.wiringbits.spra.api.models.AdminGetTables
44
import net.wiringbits.spra.ui.web.components.{CreateGuesser, EditGuesser, ListGuesser}
55
import net.wiringbits.spra.ui.web.facades.reactadmin.{Admin, Resource}
6-
import net.wiringbits.spra.ui.web.facades.simpleRestProvider
6+
import net.wiringbits.spra.ui.web.facades.createDataProvider
77
import net.wiringbits.spra.ui.web.models.DataExplorerSettings
88
import org.scalajs.macrotaskexecutor.MacrotaskExecutor.Implicits.global
99
import slinky.core.facade.{Hooks, ReactElement}
1010
import slinky.core.{FunctionalComponent, KeyAddingStage}
1111
import slinky.web.html.{div, h1}
12-
1312
import scala.util.{Failure, Success}
1413

1514
object AdminView {
@@ -52,7 +51,7 @@ object AdminView {
5251
}
5352

5453
div()(
55-
Admin(simpleRestProvider(tablesUrl))(buildResources),
54+
Admin(createDataProvider(tablesUrl))(buildResources),
5655
error.map(h1(_))
5756
)
5857
}
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
11
package net.wiringbits.spra.ui.web.facades
22

33
import scala.scalajs.js
4+
import scala.scalajs.js.annotation.JSImport
45

56
@js.native
67
trait DataProvider extends js.Object
8+
9+
@js.native
10+
@JSImport("ra-data-simple-rest", JSImport.Default)
11+
// https://www.npmjs.com/package/ra-data-simple-rest
12+
def simpleRestProvider(url: String): DataProvider = js.native
13+
14+
@js.native
15+
@JSImport("react-admin", "withLifecycleCallbacks")
16+
// https://marmelab.com/react-admin/withLifecycleCallbacks.html
17+
object WithLifecycleCallbacks extends js.Object {
18+
def apply(dataProvider: DataProvider, callbacks: js.Array[js.Object]): DataProvider = js.native
19+
}
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,32 @@
11
package net.wiringbits.spra.ui.web
22

33
import scala.scalajs.js
4-
import scala.scalajs.js.annotation.JSImport
4+
import net.wiringbits.spra.ui.web.utils.Images.*
5+
import org.scalajs.dom.File
56

67
package object facades {
7-
@js.native
8-
@JSImport("ra-data-simple-rest", JSImport.Default)
9-
def simpleRestProvider(url: String): DataProvider = js.native
8+
def createDataProvider(url: String): DataProvider = {
9+
val baseDataProvider = simpleRestProvider(url)
10+
WithLifecycleCallbacks(
11+
baseDataProvider,
12+
js.Array(
13+
js.Dynamic.literal(
14+
resource = "images",
15+
afterRead = (record: js.Dynamic, dataProvider: js.Any) => {
16+
val hexImage = record.data.asInstanceOf[String]
17+
val urlImage = convertHexToImage(hexImage)
18+
record.updateDynamic("data")(urlImage)
19+
record
20+
},
21+
beforeSave = (data: js.Dynamic, dataProvider: js.Any) => {
22+
val rawFile = data.data.rawFile.asInstanceOf[File]
23+
convertImageToByteArray(rawFile).`then` { value =>
24+
data.updateDynamic("data")(value.asInstanceOf[js.Any])
25+
data
26+
}
27+
}
28+
)
29+
)
30+
)
31+
}
1032
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package net.wiringbits.spra.ui.web.utils
2+
3+
import org.scalajs.dom
4+
import org.scalajs.dom.{Blob, File}
5+
import scala.util.{Failure, Success, Try}
6+
import scala.scalajs.js.Promise
7+
import scala.scalajs.js.typedarray.{ArrayBuffer, Int8Array, Uint8Array}
8+
import scala.scalajs.js
9+
10+
object Images {
11+
def convertImageToByteArray(image: dom.File): js.Promise[String] = {
12+
new js.Promise[String]((resolve, reject) => {
13+
val reader = new dom.FileReader()
14+
reader.onload = { (e: dom.Event) =>
15+
val arrayBuffer = reader.result.asInstanceOf[ArrayBuffer]
16+
val byteArray = new Int8Array(arrayBuffer).toArray
17+
resolve(byteArray.mkString("[", ", ", "]"))
18+
}
19+
reader.onerror = { (e: dom.Event) =>
20+
reject(new js.Error("Failed to read file"))
21+
}
22+
reader.readAsArrayBuffer(image)
23+
})
24+
}
25+
26+
def convertHexToImage(imageHex: String): String = {
27+
// Remove the "0x" prefix from the hex string, as it's not part of the actual image data
28+
val hex = imageHex.tail.tail
29+
val imageBinary: Array[Byte] =
30+
if ((hex.length % 2) == 1)
31+
Array.empty
32+
else
33+
Try(hex.grouped(2).map { hex => Integer.parseInt(hex, 16).toByte }.toArray) match {
34+
case Success(value) => value
35+
case Failure(_) => Array.empty
36+
}
37+
val byteArray = Uint8Array(js.Array(imageBinary.map(_.toShort): _*))
38+
dom.URL.createObjectURL(dom.Blob(js.Array(byteArray.buffer)))
39+
}
40+
}

0 commit comments

Comments
 (0)