From a30739440350d3b0a01f66e6ab44c35d6d2a6ed7 Mon Sep 17 00:00:00 2001 From: Andrea Minetti Date: Fri, 17 Apr 2026 08:15:41 +0200 Subject: [PATCH 1/2] Show more map context --- .../box/client/geo/BoxMapProjections.scala | 3 ++- .../ch/wsl/box/client/geo/MapParams.scala | 3 ++- .../box/client/views/components/MapList.scala | 12 ++++++++- .../components/widget/geo/OlMapWidget.scala | 6 +++-- .../scala/ch/wsl/box/rest/logic/Lookup.scala | 27 ------------------- 5 files changed, 19 insertions(+), 32 deletions(-) diff --git a/client/src/main/scala/ch/wsl/box/client/geo/BoxMapProjections.scala b/client/src/main/scala/ch/wsl/box/client/geo/BoxMapProjections.scala index c5e4593c..77b9e73a 100644 --- a/client/src/main/scala/ch/wsl/box/client/geo/BoxMapProjections.scala +++ b/client/src/main/scala/ch/wsl/box/client/geo/BoxMapProjections.scala @@ -96,7 +96,8 @@ object BoxMapConstants { None, None, None, - None + None, + Some(0.1) ) val wgs84 = MapProjection( diff --git a/client/src/main/scala/ch/wsl/box/client/geo/MapParams.scala b/client/src/main/scala/ch/wsl/box/client/geo/MapParams.scala index 138ba503..8bf6ba0e 100644 --- a/client/src/main/scala/ch/wsl/box/client/geo/MapParams.scala +++ b/client/src/main/scala/ch/wsl/box/client/geo/MapParams.scala @@ -100,7 +100,8 @@ case class MapParams( precision: Option[Double], formatters: Option[MapFormatters], enableSwisstopo: Option[Boolean], - lookups:Option[Seq[MapLookup]] + lookups:Option[Seq[MapLookup]], + minResolution: Option[Double] ) { def crs = CRS(defaultProjection) def bbox = Box2d.fromSeq(projections.find(_.name == defaultProjection).get.extent.get) diff --git a/client/src/main/scala/ch/wsl/box/client/views/components/MapList.scala b/client/src/main/scala/ch/wsl/box/client/views/components/MapList.scala index c376fa8c..beffbf0e 100644 --- a/client/src/main/scala/ch/wsl/box/client/views/components/MapList.scala +++ b/client/src/main/scala/ch/wsl/box/client/views/components/MapList.scala @@ -35,18 +35,26 @@ class MapList(_div:Div,metadata:JSONMetadata,geoms:ReadableProperty[GeoTypes.Geo import ch.wsl.box.client.Context._ import ch.wsl.box.client.Context.Implicits._ - override def allData: ReadableProperty[Json] = Property(Json.Null) + override def id: ReadableProperty[Option[String]] = Property(None) override val options: MapParams = ClientConf.mapOptions.as[MapParams].getOrElse(BoxMapConstants.defaultParams) + + val minResolution = {for{ + params <- metadata.params + js <- params.jsOpt("mapMinResolution") + minR <- js.as[Double].toOption + } yield minR}.orElse(options.minResolution).getOrElse(0.3) + val proj = new BoxMapProjections(options.projections,options.defaultProjection,options.bbox) val view = new viewMod.default(viewMod.ViewOptions() .setZoom(3) + .setMinResolution(minResolution) .setProjection(proj.defaultProjection) .setCenter(extentMod.getCenter(proj.defaultProjection.getExtent())) ) @@ -102,6 +110,8 @@ class MapList(_div:Div,metadata:JSONMetadata,geoms:ReadableProperty[GeoTypes.Geo case None => MapStyle.vectorStyle() } + + val vectorSource = new sourceMod.Vector[geomGeometryMod.default](sourceVectorMod.Options()) val featuresLayer = new layerMod.Vector(layerBaseVectorMod.Options() .setSource(vectorSource) diff --git a/client/src/main/scala/ch/wsl/box/client/views/components/widget/geo/OlMapWidget.scala b/client/src/main/scala/ch/wsl/box/client/views/components/widget/geo/OlMapWidget.scala index fd6e998d..a68b69cf 100644 --- a/client/src/main/scala/ch/wsl/box/client/views/components/widget/geo/OlMapWidget.scala +++ b/client/src/main/scala/ch/wsl/box/client/views/components/widget/geo/OlMapWidget.scala @@ -84,6 +84,7 @@ class OlMapWidget(val id: ReadableProperty[Option[String]], val field: JSONField logger.info(s"Loading ol map1") val options: MapParams = MapWidgetUtils.options(field) + val minResolution = options.minResolution.getOrElse(0.1) override def killWidget(): Unit = { super.killWidget() @@ -197,9 +198,9 @@ class OlMapWidget(val id: ReadableProperty[Option[String]], val field: JSONField val geom = new formatGeoJSONMod.default().readFeature(convertJsonToJs(geo).asInstanceOf[js.Object]).asInstanceOf[featureMod.default[geomGeometryMod.default]] vectorSource.addFeature(geom.asInstanceOf[renderFeatureMod.default]) if (geom.getGeometry().get.getType() != geomGeometryMod.Type.Point) { - view.fit(geom.getGeometry().get.getExtent(), FitOptions().setPaddingVarargs(50, 50, 50, 50)) //.setMinResolution(2)) + view.fit(geom.getGeometry().get.getExtent(), FitOptions().setPaddingVarargs(50, 50, 50, 50)) } else { - view.fit(geom.getGeometry().get.getExtent(), FitOptions().setMinResolution(2)) + view.fit(geom.getGeometry().get.getExtent(), FitOptions()) } } else { logger.debug(s"Fit with default extent: ${defaultProjection.getExtent().mkString(",")}") @@ -276,6 +277,7 @@ class OlMapWidget(val id: ReadableProperty[Option[String]], val field: JSONField view = new viewMod.default(viewMod.ViewOptions() .setZoom(3) + .setMinResolution(minResolution) .setProjection(defaultProjection) .setCenter(extentMod.getCenter(defaultProjection.getExtent())) ) diff --git a/server/src/main/scala/ch/wsl/box/rest/logic/Lookup.scala b/server/src/main/scala/ch/wsl/box/rest/logic/Lookup.scala index 7d6be033..d481e471 100755 --- a/server/src/main/scala/ch/wsl/box/rest/logic/Lookup.scala +++ b/server/src/main/scala/ch/wsl/box/rest/logic/Lookup.scala @@ -13,33 +13,6 @@ import scala.concurrent.{Await, ExecutionContext, Future} object Lookup { - - - import ch.wsl.box.shared.utils.JSONUtils._ - - private def remoteLookups(metadata: JSONMetadata):Seq[JSONFieldLookupRemote] = metadata.fields.flatMap(_.remoteLookup) - - def valuesForEntity(metadata:JSONMetadata)(implicit ec: ExecutionContext, mat:Materializer,services: Services,db:FullDatabase) :Future[Map[String,Seq[Json]]] = Future{ - - val z:Seq[(String,Seq[Json])] = Seq[(String,Seq[Json])]() - - remoteLookups(metadata).map(_.lookupEntity).foldRight(z){ case (lookupEntity,acc) => - def le:Future[(String,Seq[Json])] = db.db.run{ - Registry().actions(lookupEntity).findSimple(JSONQuery.empty.limit(10000)).map{ jq => (lookupEntity,jq) } - } - - acc ++ Seq(Await.result(le,10.seconds)) - - }.toMap - -// DBIO.sequence{ -// remoteLookups(metadata).map(_.lookupEntity).map{ lookupEntity => -// Registry().actions(lookupEntity).findSimple(JSONQuery.empty.limit(10000)).map{ jq => lookupEntity -> jq} -// } -// }.map(_.toMap) - - } - def values(entity:String, foreign:JSONFieldMapForeign, query:JSONQuery)(implicit ec: ExecutionContext, mat:Materializer, services: Services) :DBIO[Seq[JSONLookup]] = { Registry().actions(entity).findSimple(query).map{ _.map{ row => JSONFieldLookup.toJsonLookup(foreign)(row) From a3bbec9fe1403066350ac0ea96440299ea584dfb Mon Sep 17 00:00:00 2001 From: Andrea Minetti Date: Tue, 21 Apr 2026 10:43:38 +0200 Subject: [PATCH 2/2] Enable public functions --- db/box_migrations/BOX_V65__add_public_functions.sql | 1 + .../scala/ch/wsl/box/model/boxentities/BoxFunction.scala | 7 ++++--- .../src/main/scala/ch/wsl/box/rest/routes/Functions.scala | 7 +++++-- .../main/scala/ch/wsl/box/rest/routes/v1/PrivateArea.scala | 2 +- .../main/scala/ch/wsl/box/rest/routes/v1/PublicArea.scala | 7 ++++++- 5 files changed, 17 insertions(+), 7 deletions(-) create mode 100644 db/box_migrations/BOX_V65__add_public_functions.sql diff --git a/db/box_migrations/BOX_V65__add_public_functions.sql b/db/box_migrations/BOX_V65__add_public_functions.sql new file mode 100644 index 00000000..50caa859 --- /dev/null +++ b/db/box_migrations/BOX_V65__add_public_functions.sql @@ -0,0 +1 @@ +alter table function add public boolean default false; \ No newline at end of file diff --git a/server/src/main/scala/ch/wsl/box/model/boxentities/BoxFunction.scala b/server/src/main/scala/ch/wsl/box/model/boxentities/BoxFunction.scala index 08a115d2..dae355fe 100644 --- a/server/src/main/scala/ch/wsl/box/model/boxentities/BoxFunction.scala +++ b/server/src/main/scala/ch/wsl/box/model/boxentities/BoxFunction.scala @@ -10,14 +10,15 @@ object BoxFunction { private val schema = Some(Registry.box().schema) - case class BoxFunction_row(function_uuid: Option[java.util.UUID] = None, name: String, mode:String, function:String, presenter:Option[String], description: Option[String] = None, layout: Option[String] = None, order: Option[Double], access_role:Option[List[String]]) + case class BoxFunction_row(function_uuid: Option[java.util.UUID] = None, name: String, mode:String, function:String, presenter:Option[String], description: Option[String] = None, layout: Option[String] = None, order: Option[Double], access_role:Option[List[String]], public:Boolean) class BoxFunction(_tableTag: Tag) extends Table[BoxFunction_row](_tableTag,schema, "function") { - def * = (Rep.Some(function_uuid), name, mode, function, presenter, description, layout, order ,access_role) <> (BoxFunction_row.tupled, BoxFunction_row.unapply) - def ? = (Rep.Some(function_uuid), name, mode, function, presenter, description, layout, order, access_role).shaped.<>({ r=>import r._; _1.map(_=> BoxFunction_row.tupled((_1, _2, _3, _4, _5, _6, _7, _8, _9)))}, (_:Any) => throw new Exception("Inserting into ? projection not supported.")) + def * = (Rep.Some(function_uuid), name, mode, function, presenter, description, layout, order ,access_role, public) <> (BoxFunction_row.tupled, BoxFunction_row.unapply) + def ? = (Rep.Some(function_uuid), name, mode, function, presenter, description, layout, order, access_role, public).shaped.<>({ r=>import r._; _1.map(_=> BoxFunction_row.tupled((_1, _2, _3, _4, _5, _6, _7, _8, _9, _10)))}, (_:Any) => throw new Exception("Inserting into ? projection not supported.")) val function_uuid: Rep[java.util.UUID] = column[java.util.UUID]("function_uuid", O.AutoInc, O.PrimaryKey) val name: Rep[String] = column[String]("name") + val public: Rep[Boolean] = column[Boolean]("public") val mode: Rep[String] = column[String]("mode", O.Default(FunctionKind.Modes.TABLE)) val function: Rep[String] = column[String]("function") val presenter: Rep[Option[String]] = column[Option[String]]("presenter", O.Default(None)) diff --git a/server/src/main/scala/ch/wsl/box/rest/routes/Functions.scala b/server/src/main/scala/ch/wsl/box/rest/routes/Functions.scala index 0604d153..e5be1701 100644 --- a/server/src/main/scala/ch/wsl/box/rest/routes/Functions.scala +++ b/server/src/main/scala/ch/wsl/box/rest/routes/Functions.scala @@ -12,7 +12,7 @@ import io.circe.Json import scala.concurrent.{ExecutionContext, Future} -case class Functions()(implicit val up: UserProfile, val mat: Materializer, val ec: ExecutionContext, val system:ActorSystem, val services:Services) extends Data { +case class Functions(public:Boolean)(implicit val up: UserProfile, val mat: Materializer, val ec: ExecutionContext, val system:ActorSystem, val services:Services) extends Data { import ch.wsl.box.jdbc.PostgresProfile.api._ import ch.wsl.box.shared.utils.JSONUtils._ @@ -28,7 +28,10 @@ case class Functions()(implicit val up: UserProfile, val mat: Materializer, val for{ functionDef <- services.connection.adminDB.run{ - functions.BoxFunctionTable.filter(_.name === function).result + if(public) + functions.BoxFunctionTable.filter(x => x.name === function && x.public).result + else + functions.BoxFunctionTable.filter(x => x.name === function).result }.map(_.headOption) result <- functionDef match { case None => Future.successful(None) diff --git a/server/src/main/scala/ch/wsl/box/rest/routes/v1/PrivateArea.scala b/server/src/main/scala/ch/wsl/box/rest/routes/v1/PrivateArea.scala index 6e88f9b1..b1a486ab 100644 --- a/server/src/main/scala/ch/wsl/box/rest/routes/v1/PrivateArea.scala +++ b/server/src/main/scala/ch/wsl/box/rest/routes/v1/PrivateArea.scala @@ -39,7 +39,7 @@ class PrivateArea(implicit ec:ExecutionContext, sessionManager: SessionManager[B } def function(implicit up:UserProfile) = pathPrefix("function") { - Functions().route + Functions(public = false).route } def file(implicit up:UserProfile) = pathPrefix("file") { diff --git a/server/src/main/scala/ch/wsl/box/rest/routes/v1/PublicArea.scala b/server/src/main/scala/ch/wsl/box/rest/routes/v1/PublicArea.scala index 01fd06e1..58788859 100644 --- a/server/src/main/scala/ch/wsl/box/rest/routes/v1/PublicArea.scala +++ b/server/src/main/scala/ch/wsl/box/rest/routes/v1/PublicArea.scala @@ -10,7 +10,7 @@ import ch.wsl.box.rest.utils.{Auth, UserProfile} import ch.wsl.box.jdbc.PostgresProfile.api._ import ch.wsl.box.model.shared.EntityKind import ch.wsl.box.rest.metadata.FormMetadataFactory -import ch.wsl.box.rest.routes.Form +import ch.wsl.box.rest.routes.{Form, Functions} import ch.wsl.box.rest.runtime.Registry import ch.wsl.box.services.Services @@ -45,6 +45,10 @@ class PublicArea(implicit ec:ExecutionContext, mat:Materializer, system:ActorSys } } + def function(implicit up:UserProfile) = pathPrefix("function") { + Functions(public = true).route + } + def form:Route = pathPrefix(EntityKind.FORM.kind) { pathPrefix(Segment) { lang => pathPrefix(Segment) { name => @@ -88,6 +92,7 @@ class PublicArea(implicit ec:ExecutionContext, mat:Materializer, system:ActorSys val route:Route = pathPrefix("public") { form ~ file ~ + function ~ pathPrefix("entity") { // keep same path structure than private API pathPrefix(Segment) { lang => entityRoute