Skip to content

Commit

Permalink
Added roles/permissions UI
Browse files Browse the repository at this point in the history
  • Loading branch information
viktor-podzigun committed Aug 27, 2018
1 parent a120c02 commit 2708b2b
Show file tree
Hide file tree
Showing 12 changed files with 282 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@ import org.scalajs.dom
import scommons.admin.client.api.AdminUiApiClient
import scommons.admin.client.company.CompanyActions
import scommons.admin.client.role.RoleActions
import scommons.admin.client.role.permission.RolePermissionActions
import scommons.admin.client.system.SystemActions
import scommons.admin.client.system.group.SystemGroupActions
import scommons.api.http.js.JsApiHttpClient

trait AdminActions extends CompanyActions
object AdminActions extends CompanyActions
with SystemGroupActions
with SystemActions
with RoleActions {
with RoleActions
with RolePermissionActions {

private val baseUrl = {
val loc = dom.window.location
Expand All @@ -22,5 +24,3 @@ trait AdminActions extends CompanyActions
new AdminUiApiClient(new JsApiHttpClient(baseUrl))
}
}

object AdminActions extends AdminActions
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import io.github.shogowada.scalajs.reactjs.router.dom.RouterDOM._
import org.scalajs.dom
import scommons.admin.client.company.CompanyController
import scommons.admin.client.role.RoleController
import scommons.admin.client.role.permission.RolePermissionController
import scommons.admin.client.system.SystemController
import scommons.admin.client.system.group.SystemGroupController
import scommons.client.app._
Expand Down Expand Up @@ -37,11 +38,13 @@ object AdminMain {
val envController = new SystemGroupController(apiActions, apiActions)
val appController = new SystemController(apiActions)
val roleController = new RoleController(apiActions)
val rolePermissionController = new RolePermissionController(apiActions)
val routeController = new AdminRouteController(
companyController,
envController,
appController,
roleController
roleController,
rolePermissionController
)

ReactDOM.render(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import io.github.shogowada.scalajs.reactjs.redux.Redux.Dispatch
import scommons.admin.client.AdminRouteController._
import scommons.admin.client.company.CompanyController
import scommons.admin.client.role.RoleController
import scommons.admin.client.role.permission.RolePermissionController
import scommons.admin.client.system.SystemController
import scommons.admin.client.system.group.SystemGroupController
import scommons.client.app._
Expand All @@ -16,7 +17,8 @@ import scommons.client.util.BrowsePath
class AdminRouteController(companyController: CompanyController,
systemGroupController: SystemGroupController,
systemController: SystemController,
roleController: RoleController
roleController: RoleController,
rolePermissionController: RolePermissionController
) extends BaseStateController[AdminStateDef, AppBrowseControllerProps] {

lazy val uiComponent: UiComponent[AppBrowseControllerProps] = AppBrowseController
Expand Down Expand Up @@ -51,7 +53,7 @@ class AdminRouteController(companyController: CompanyController,
systemNode.copy(
children = List(
rolesNode.copy(
children = roles.map(roleController.getRoleItem(rolesNode.path, _))
children = roles.map(roleController.getRoleItem(rolesNode.path, _, rolePermissionController))
)
)
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package scommons.admin.client

import scommons.admin.client.company.{CompanyState, CompanyStateReducer}
import scommons.admin.client.role.permission.{RolePermissionState, RolePermissionStateReducer}
import scommons.admin.client.role.{RoleState, RoleStateReducer}
import scommons.admin.client.system.group.{SystemGroupState, SystemGroupStateReducer}
import scommons.admin.client.system.{SystemState, SystemStateReducer}
Expand All @@ -14,13 +15,15 @@ trait AdminStateDef {
def systemGroupState: SystemGroupState
def systemState: SystemState
def roleState: RoleState
def rolePermissionState: RolePermissionState
}

case class AdminState(currentTask: Option[AbstractTaskKey],
companyState: CompanyState,
systemGroupState: SystemGroupState,
systemState: SystemState,
roleState: RoleState) extends AdminStateDef
roleState: RoleState,
rolePermissionState: RolePermissionState) extends AdminStateDef

object AdminStateReducer {

Expand All @@ -29,7 +32,8 @@ object AdminStateReducer {
companyState = CompanyStateReducer(state.map(_.companyState), action),
systemGroupState = SystemGroupStateReducer(state.map(_.systemGroupState), action),
systemState = SystemStateReducer(state.map(_.systemState), action),
roleState = RoleStateReducer(state.map(_.roleState), action)
roleState = RoleStateReducer(state.map(_.roleState), action),
rolePermissionState = RolePermissionStateReducer(state.map(_.rolePermissionState), action)
)

private def currentTaskReducer(currentTask: Option[AbstractTaskKey],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import io.github.shogowada.scalajs.reactjs.redux.Redux.Dispatch
import scommons.admin.client.AdminRouteController._
import scommons.admin.client.api.role.RoleData
import scommons.admin.client.role.RoleActions._
import scommons.admin.client.role.permission.RolePermissionController
import scommons.admin.client.{AdminImagesCss, AdminStateDef}
import scommons.client.controller.{BaseStateAndRouteController, RouteParams}
import scommons.client.ui.tree.{BrowseTreeItemData, BrowseTreeNodeData}
Expand Down Expand Up @@ -50,8 +51,14 @@ class RoleController(apiActions: RoleActions)
path = path
)

def getRoleItem(path: BrowsePath, data: RoleData): BrowseTreeItemData = roleItem.copy(
text = data.title,
path = BrowsePath(s"$path/${data.id.get}")
)
def getRoleItem(path: BrowsePath,
data: RoleData,
rolePermissionController: RolePermissionController): BrowseTreeItemData = {

roleItem.copy(
text = data.title,
path = BrowsePath(s"$path/${data.id.get}"),
reactClass = Some(rolePermissionController())
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package scommons.admin.client.role.permission

import io.github.shogowada.scalajs.reactjs.redux.Action
import io.github.shogowada.scalajs.reactjs.redux.Redux.Dispatch
import scommons.admin.client.api.role.permission._
import scommons.admin.client.role.RoleActions.RoleUpdatedAction
import scommons.admin.client.role.permission.RolePermissionActions._
import scommons.api.ApiStatus.Ok
import scommons.client.task.{FutureTask, TaskAction}

import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.Success

trait RolePermissionActions {

protected def client: RolePermissionApi

def rolePermissionsFetch(dispatch: Dispatch, roleId: Int): RolePermissionFetchAction = {
val future = client.listRolePermissions(roleId).andThen {
case Success(RolePermissionResp(Ok, Some(respData))) =>
dispatch(RoleUpdatedAction(respData.role))
dispatch(RolePermissionFetchedAction(respData))
}

RolePermissionFetchAction(FutureTask("Fetching Role Permissions", future))
}

def rolePermissionsAdd(dispatch: Dispatch, roleId: Int, data: RolePermissionUpdateReq): RolePermissionAddAction = {
val future = client.addRolePermissions(roleId, data).andThen {
case Success(RolePermissionResp(Ok, Some(respData))) =>
dispatch(RoleUpdatedAction(respData.role))
dispatch(RolePermissionAddedAction(respData))
}

RolePermissionAddAction(FutureTask("Adding Role Permissions", future))
}

def rolePermissionsRemove(dispatch: Dispatch, roleId: Int, data: RolePermissionUpdateReq): RolePermissionRemoveAction = {
val future = client.removeRolePermissions(roleId, data).andThen {
case Success(RolePermissionResp(Ok, Some(respData))) =>
dispatch(RoleUpdatedAction(respData.role))
dispatch(RolePermissionRemovedAction(respData))
}

RolePermissionRemoveAction(FutureTask("Removing Role Permissions", future))
}
}

object RolePermissionActions {

case class RolePermissionFetchAction(task: FutureTask[RolePermissionResp]) extends TaskAction
case class RolePermissionFetchedAction(data: RolePermissionRespData) extends Action

case class RolePermissionAddAction(task: FutureTask[RolePermissionResp]) extends TaskAction
case class RolePermissionAddedAction(data: RolePermissionRespData) extends Action

case class RolePermissionRemoveAction(task: FutureTask[RolePermissionResp]) extends TaskAction
case class RolePermissionRemovedAction(data: RolePermissionRespData) extends Action
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package scommons.admin.client.role.permission

import io.github.shogowada.scalajs.reactjs.redux.Redux.Dispatch
import scommons.admin.client.AdminRouteController._
import scommons.admin.client.AdminStateDef
import scommons.client.controller.{BaseStateAndRouteController, RouteParams}
import scommons.client.ui.UiComponent

class RolePermissionController(apiActions: RolePermissionActions)
extends BaseStateAndRouteController[AdminStateDef, RolePermissionPanelProps] {

lazy val uiComponent: UiComponent[RolePermissionPanelProps] = RolePermissionPanel

def mapStateAndRouteToProps(dispatch: Dispatch,
state: AdminStateDef,
routeParams: RouteParams): RolePermissionPanelProps = {

val pathParams = routeParams.pathParams

RolePermissionPanelProps(dispatch, apiActions, state.rolePermissionState,
extractRoleId(pathParams).getOrElse(-1))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package scommons.admin.client.role.permission

import io.github.shogowada.scalajs.reactjs.React
import io.github.shogowada.scalajs.reactjs.VirtualDOM._
import io.github.shogowada.scalajs.reactjs.classes.ReactClass
import io.github.shogowada.scalajs.reactjs.redux.Redux.Dispatch
import scommons.admin.client.AdminImagesCss
import scommons.admin.client.api.role.permission.{RolePermissionData, RolePermissionUpdateReq}
import scommons.client.ui._
import scommons.client.ui.tree._

case class RolePermissionPanelProps(dispatch: Dispatch,
actions: RolePermissionActions,
state: RolePermissionState,
selectedRoleId: Int)

object RolePermissionPanel extends UiComponent[RolePermissionPanelProps] {

def apply(): ReactClass = reactClass
lazy val reactClass: ReactClass = createComp

private def createComp = React.createClass[PropsType, Unit](
componentDidMount = { self =>
val props = self.props.wrapped
if (!props.state.role.flatMap(_.id).contains(props.selectedRoleId)) {
props.dispatch(props.actions.rolePermissionsFetch(props.dispatch, props.selectedRoleId))
}
},
componentDidUpdate = { (self, prevProps, _) =>
val props = self.props.wrapped
if (props.selectedRoleId != prevProps.wrapped.selectedRoleId) {
props.dispatch(props.actions.rolePermissionsFetch(props.dispatch, props.selectedRoleId))
}
},
render = { self =>
val props = self.props.wrapped

val roots = buildTree(props.state.permissionsByParentId)

<(CheckBoxTree())(^.wrapped := CheckBoxTreeProps(
roots = roots,
onChange = { (data, value) =>
val permissionId = data.key.toInt
val ids = getAllDescendantIds(permissionId, props.state.permissionsByParentId)

props.state.role.flatMap(_.version).foreach { roleVersion =>
if (TriState.isSelected(value)) {
props.dispatch(props.actions.rolePermissionsAdd(
props.dispatch,
props.selectedRoleId,
RolePermissionUpdateReq(ids, roleVersion)
))
}
else {
props.dispatch(props.actions.rolePermissionsRemove(
props.dispatch,
props.selectedRoleId,
RolePermissionUpdateReq(ids, roleVersion)
))
}
}
},
openNodes = roots.map(_.key).toSet
))()
}
)

private def getAllDescendantIds(id: Int,
permissions: Map[Option[Int], List[RolePermissionData]]): Set[Int] = {

def loop(dataList: List[RolePermissionData], ids: Set[Int]): Set[Int] = dataList match {
case Nil => ids
case head :: tail =>
val results = loop(tail, ids + head.id)
if (head.isNode) {
val children = permissions.getOrElse(Some(head.id), Nil)
results ++ loop(children, Set.empty)
}
else results
}

loop(permissions.getOrElse(Some(id), Nil), Set(id))
}

private def buildTree(permissions: Map[Option[Int], List[RolePermissionData]]): List[CheckBoxTreeData] = {

def calcValue(children: List[CheckBoxTreeData], defaultValue: TriState): TriState = {
val values = children.map(_.value).toSet
if (values.size > 1) TriState.Indeterminate
else {
values.headOption match {
case None => defaultValue
case Some(v) => v
}
}
}

def loop(dataList: List[RolePermissionData]): List[CheckBoxTreeData] = dataList.map { p =>
val key = p.id.toString
val value = if (p.isEnabled) TriState.Selected else TriState.Deselected
val text = p.title
if (p.isNode) {
val children = loop(permissions.getOrElse(Some(p.id), Nil))
CheckBoxTreeNodeData(key, calcValue(children, value), text, None, children)
}
else CheckBoxTreeItemData(key, value, text, Some(AdminImagesCss.keySmall))
}

loop(permissions.getOrElse(None, Nil))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package scommons.admin.client.role.permission

import scommons.admin.client.api.role.RoleData
import scommons.admin.client.api.role.permission.RolePermissionData
import scommons.admin.client.role.permission.RolePermissionActions._

case class RolePermissionState(permissionsByParentId: Map[Option[Int], List[RolePermissionData]] = Map.empty,
role: Option[RoleData] = None) {

def getPermissions(parentId: Option[Int]): List[RolePermissionData] = {
permissionsByParentId.getOrElse(parentId, Nil)
}
}

object RolePermissionStateReducer {

def apply(state: Option[RolePermissionState], action: Any): RolePermissionState = {
reduce(state.getOrElse(RolePermissionState()), action)
}

private def reduce(state: RolePermissionState, action: Any): RolePermissionState = action match {
case RolePermissionFetchedAction(data) => state.copy(
permissionsByParentId = data.permissions.groupBy(_.parentId),
role = Some(data.role)
)
case RolePermissionAddedAction(data) => state.copy(
permissionsByParentId = data.permissions.groupBy(_.parentId),
role = Some(data.role)
)
case RolePermissionRemovedAction(data) => state.copy(
permissionsByParentId = data.permissions.groupBy(_.parentId),
role = Some(data.role)
)
case _ => state
}
}

0 comments on commit 2708b2b

Please sign in to comment.