From 9d14834909248d970e097f416e44147e8a2dffc3 Mon Sep 17 00:00:00 2001 From: Juha Paananen Date: Mon, 16 Oct 2023 20:43:16 +0300 Subject: [PATCH] Simplify --- frontend/src/board/BoardView.tsx | 1 - frontend/src/board/board-permissions.ts | 76 +++++++------------ .../src/board/contextmenu/ContextMenuView.tsx | 6 +- frontend/src/board/contextmenu/alignments.tsx | 7 +- frontend/src/board/contextmenu/areaTiling.tsx | 10 +-- .../src/board/contextmenu/colorsAndShapes.tsx | 7 +- .../src/board/contextmenu/connection-ends.tsx | 9 +-- frontend/src/board/contextmenu/fontSizes.tsx | 5 +- frontend/src/board/contextmenu/lock.tsx | 13 +++- .../src/board/contextmenu/textAlignments.tsx | 6 +- frontend/src/store/board-store.ts | 4 - 11 files changed, 59 insertions(+), 85 deletions(-) diff --git a/frontend/src/board/BoardView.tsx b/frontend/src/board/BoardView.tsx index 59929c1f5..bebd4189f 100644 --- a/frontend/src/board/BoardView.tsx +++ b/frontend/src/board/BoardView.tsx @@ -317,7 +317,6 @@ export const BoardView = ({ board, focus, viewRect, - permissions: boardStore.permissionsProvider, }} /> diff --git a/frontend/src/board/board-permissions.ts b/frontend/src/board/board-permissions.ts index 094a4dbb8..5dcd523ab 100644 --- a/frontend/src/board/board-permissions.ts +++ b/frontend/src/board/board-permissions.ts @@ -1,63 +1,39 @@ -import * as L from "lonna" -import { AccessLevel, Connection, Id, Item } from "../../../common/src/domain" -import { componentScope } from "harmaja" +import { Connection, Item } from "../../../common/src/domain" export interface ItemPermissions { canChangeFont: boolean canChangeShapeAndColor: boolean canChangeTextAlign: boolean canMove: boolean + canLock: boolean } export interface ConnectionPermissions { canMove: boolean canChangeShapeAndColor: boolean + canLock: boolean } -export type BoardPermissionsProvider = ReturnType -export function BoardPermissionsProvider(accessLevel: L.Property) { - const api = { - accessLevel, - getItemPermissions(itemId: Id): L.Property { - return L.constant({ - canChangeFont: false, - canChangeShapeAndColor: false, - canChangeTextAlign: false, - canMove: false, - }) - }, - getConnectionPermissions(connectionId: Id): L.Property { - return L.constant({ - canMove: false, - canChangeShapeAndColor: false, - }) - }, - everyItemHasPermission( - p: L.Property, - fn: (p: ItemPermissions) => boolean, - scope: L.Scope, - ): L.Property { - return p - .pipe( - L.flatMapLatest((items) => - L.view(L.combineAsArray(items.map((i) => api.getItemPermissions(i.id))), (xs) => xs.every(fn)), - ), - ) - .applyScope(scope) - }, - everyConnectionHasPermission( - p: L.Property, - fn: (p: ConnectionPermissions) => boolean, - scope: L.Scope, - ): L.Property { - return p - .pipe( - L.flatMapLatest((items) => - L.view(L.combineAsArray(items.map((i) => api.getConnectionPermissions(i.id))), (xs) => - xs.every(fn), - ), - ), - ) - .applyScope(scope) - }, + +export function anyItemHasPermission(items: Item[], f: (p: ItemPermissions) => boolean): boolean { + return items.some((i) => f(getItemPermissions(i))) +} + +export function anyConnectionHasPermission(items: Connection[], f: (p: ConnectionPermissions) => boolean): boolean { + return items.some((i) => f(getConnectionPermissions(i))) +} + +export function getItemPermissions(item: Item): ItemPermissions { + return { + canChangeFont: false, + canChangeShapeAndColor: false, + canChangeTextAlign: false, + canMove: false, + canLock: true, + } +} +export function getConnectionPermissions(connection: Connection): ConnectionPermissions { + return { + canMove: false, + canChangeShapeAndColor: false, + canLock: true, } - return api } diff --git a/frontend/src/board/contextmenu/ContextMenuView.tsx b/frontend/src/board/contextmenu/ContextMenuView.tsx index df1310b08..10807dbbd 100644 --- a/frontend/src/board/contextmenu/ContextMenuView.tsx +++ b/frontend/src/board/contextmenu/ContextMenuView.tsx @@ -13,14 +13,12 @@ import { resolveEndpoint } from "../../../../common/src/connection-utils" import { connectionEndsMenu } from "./connection-ends" import { textAlignmentsMenu } from "./textAlignments" import { lockMenu } from "./lock" -import { BoardPermissionsProvider } from "../board-permissions" export type SubmenuProps = { focusedItems: L.Property<{ items: Item[]; connections: Connection[] }> board: L.Property dispatch: Dispatch submenu: L.Atom - permissions: BoardPermissionsProvider } export type SubMenuCreator = (props: SubmenuProps) => HarmajaOutput @@ -76,13 +74,11 @@ export const ContextMenuView = ({ board, focus, viewRect, - permissions, }: { dispatch: Dispatch board: L.Property focus: L.Property viewRect: L.Property - permissions: BoardPermissionsProvider }) => { const focusedItems = L.view(focus, board, (f, b) => { if (f.status === "dragging" || f.status === "connection-adding" || f.status === "adding") @@ -97,7 +93,7 @@ export const ContextMenuView = ({ (i) => i?.id, ).forEach(() => submenu.set(null)) - const props = { board, focusedItems, dispatch, submenu, permissions } + const props = { board, focusedItems, dispatch, submenu } const widgetCreators = [ alignmentsMenu("x", props), alignmentsMenu("y", props), diff --git a/frontend/src/board/contextmenu/alignments.tsx b/frontend/src/board/contextmenu/alignments.tsx index 3c4ed3f4b..5b343154a 100644 --- a/frontend/src/board/contextmenu/alignments.tsx +++ b/frontend/src/board/contextmenu/alignments.tsx @@ -13,6 +13,7 @@ import { VerticalDistributeIcon, } from "../../components/Icons" import { SubmenuProps } from "./ContextMenuView" +import { anyItemHasPermission } from "../board-permissions" const createSubMenuByAxis = (axis: Axis) => (props: SubmenuProps) => { return
{alignmentsSubMenu(axis, props)}
@@ -22,11 +23,7 @@ export function alignmentsMenu(axis: Axis, props: SubmenuProps) { const hasItemsToAlign = L.view(props.focusedItems, (items) => items.items.length > 1) const hasItemsToDistribute = L.view(props.focusedItems, (items) => items.items.length > 2) const createSubmenu = createSubMenuByAxis(axis) - const enabled = props.permissions.everyItemHasPermission( - props.focusedItems.pipe(L.map((i) => i.items)), - (p) => p.canMove, - componentScope(), - ) + const enabled = L.view(props.focusedItems, (items) => anyItemHasPermission(items.items, (p) => p.canMove)) return L.combine(hasItemsToAlign, hasItemsToDistribute, (hasItemsToAlign, hasItemsToDistribute) => { return !hasItemsToAlign && !hasItemsToDistribute diff --git a/frontend/src/board/contextmenu/areaTiling.tsx b/frontend/src/board/contextmenu/areaTiling.tsx index da9ffce5a..ac27ac6bd 100644 --- a/frontend/src/board/contextmenu/areaTiling.tsx +++ b/frontend/src/board/contextmenu/areaTiling.tsx @@ -1,13 +1,13 @@ -import { componentScope, h } from "harmaja" +import { h } from "harmaja" import * as L from "lonna" -import { Board, Container, findItem, isContainer, Item } from "../../../../common/src/domain" +import { Board, Container, Item, findItem, isContainer } from "../../../../common/src/domain" import { TileIcon } from "../../components/Icons" -import { Dispatch } from "../../store/board-store" +import { anyItemHasPermission } from "../board-permissions" import { contentRect, organizeItems, packableItems } from "../item-organizer" import { packItems } from "../item-packer" import { SubmenuProps } from "./ContextMenuView" -export function areaTilingMenu({ board, focusedItems, dispatch, permissions }: SubmenuProps) { +export function areaTilingMenu({ board, focusedItems, dispatch }: SubmenuProps) { const packables = L.view(focusedItems, (items) => { if (items.items.length === 1) { if (isContainer(items.items[0])) return items.items @@ -18,7 +18,7 @@ export function areaTilingMenu({ board, focusedItems, dispatch, permissions }: S } return [] }) - const enabled = permissions.everyItemHasPermission(packables, (p) => p.canMove, componentScope()) + const enabled = L.view(packables, (items) => anyItemHasPermission(items, (p) => p.canMove)) const className = enabled.pipe(L.map((e) => (e ? "icon" : "icon disabled"))) return L.view( diff --git a/frontend/src/board/contextmenu/colorsAndShapes.tsx b/frontend/src/board/contextmenu/colorsAndShapes.tsx index 0d8b671ec..c1c5a7ac6 100644 --- a/frontend/src/board/contextmenu/colorsAndShapes.tsx +++ b/frontend/src/board/contextmenu/colorsAndShapes.tsx @@ -6,6 +6,7 @@ import { colorsSubMenu } from "./colors" import { SubmenuProps } from "./ContextMenuView" import { getShapeIcon, shapesSubMenu } from "./shapes" import { disabledColor } from "../../components/UIColors" +import { anyItemHasPermission } from "../board-permissions" function createSubMenu(props: SubmenuProps) { return ( @@ -19,11 +20,7 @@ function createSubMenu(props: SubmenuProps) { export function colorsAndShapesMenu(props: SubmenuProps) { const coloredItems = L.view(props.focusedItems, (items) => items.items.filter(isColoredItem)) const representativeColoredItem: L.Property = L.view(coloredItems, (items) => items[0] || null) - const enabled = props.permissions.everyItemHasPermission( - coloredItems, - (p) => p.canChangeShapeAndColor, - componentScope(), - ) + const enabled = L.view(coloredItems, (items) => anyItemHasPermission(items, (p) => p.canChangeShapeAndColor)) return L.view(representativeColoredItem, enabled, (item, enabled) => { if (!item) return [] const color = NOTE_COLORS.find((c) => c.color === item.color) || NOTE_COLORS[0] diff --git a/frontend/src/board/contextmenu/connection-ends.tsx b/frontend/src/board/contextmenu/connection-ends.tsx index 8d02e6ba2..5cef4b5b6 100644 --- a/frontend/src/board/contextmenu/connection-ends.tsx +++ b/frontend/src/board/contextmenu/connection-ends.tsx @@ -13,6 +13,7 @@ import { ConnectionRightDotIcon, } from "../../components/Icons" import { SubmenuProps } from "./ContextMenuView" +import { anyConnectionHasPermission } from "../board-permissions" const styles: ConnectionEndStyle[] = ["arrow", "black-dot", "none"] function nextStyle(style: ConnectionEndStyle) { @@ -20,15 +21,13 @@ function nextStyle(style: ConnectionEndStyle) { return styles[(i + 1) % styles.length] } -export function connectionEndsMenu({ board, focusedItems, dispatch, permissions }: SubmenuProps) { +export function connectionEndsMenu({ board, focusedItems, dispatch }: SubmenuProps) { const connections = L.view(focusedItems, (items) => items.connections) const singleConnection = L.view(connections, (connections) => connections.length === 1 && connections[0].action === "connect" ? connections[0] : null, ) - const enabled = permissions.everyConnectionHasPermission( - connections, - (p) => p.canChangeShapeAndColor, - componentScope(), + const enabled = L.view(connections, (connections) => + anyConnectionHasPermission(connections, (p) => p.canChangeShapeAndColor), ) const className = enabled.pipe(L.map((e) => (e ? "icon" : "icon disabled"))) diff --git a/frontend/src/board/contextmenu/fontSizes.tsx b/frontend/src/board/contextmenu/fontSizes.tsx index 5a49400cd..4b85a460a 100644 --- a/frontend/src/board/contextmenu/fontSizes.tsx +++ b/frontend/src/board/contextmenu/fontSizes.tsx @@ -4,6 +4,7 @@ import { isTextItem } from "../../../../common/src/domain" import { DecreaseFontSizeIcon, IncreaseFontSizeIcon } from "../../components/Icons" import { SubmenuProps } from "./ContextMenuView" import { black, disabledColor } from "../../components/UIColors" +import { anyItemHasPermission } from "../board-permissions" type MenuIconProps = { onClick: () => void @@ -24,10 +25,10 @@ export const MenuIcon = (props: MenuIconProps) => { ) } -export function fontSizesMenu({ board, focusedItems, dispatch, permissions }: SubmenuProps) { +export function fontSizesMenu({ board, focusedItems, dispatch }: SubmenuProps) { const textItems = L.view(focusedItems, (items) => items.items.filter(isTextItem)) const anyText = L.view(textItems, (items) => items.length > 0) - const enabled = permissions.everyItemHasPermission(textItems, (p) => p.canChangeFont, componentScope()) + const enabled = L.view(textItems, (items) => anyItemHasPermission(items, (p) => p.canChangeFont)) const className = enabled.pipe(L.map((e) => (e ? "icon" : "icon disabled"))) return L.view(anyText, (any) => diff --git a/frontend/src/board/contextmenu/lock.tsx b/frontend/src/board/contextmenu/lock.tsx index ba24c2cdc..defd795ba 100644 --- a/frontend/src/board/contextmenu/lock.tsx +++ b/frontend/src/board/contextmenu/lock.tsx @@ -2,8 +2,15 @@ import { h } from "harmaja" import * as L from "lonna" import { LockIcon } from "../../components/Icons" import { SubmenuProps } from "./ContextMenuView" +import { anyConnectionHasPermission, anyItemHasPermission } from "../board-permissions" export function lockMenu({ board, focusedItems, dispatch }: SubmenuProps) { + const enabled = L.view( + focusedItems, + (items) => + anyItemHasPermission(items.items, (p) => p.canLock) || + anyConnectionHasPermission(items.connections, (p) => p.canLock), + ) return L.view( focusedItems, (ps) => ps.connections.length > 0 || ps.items.length > 0, @@ -12,7 +19,11 @@ export function lockMenu({ board, focusedItems, dispatch }: SubmenuProps) { return show ? [
- console.log("TODO")}> + (e ? "icon" : "icon disabled"))} + title="Lock item(s)" + onClick={() => console.log("TODO")} + >
, diff --git a/frontend/src/board/contextmenu/textAlignments.tsx b/frontend/src/board/contextmenu/textAlignments.tsx index cab06b9c7..f76e6da38 100644 --- a/frontend/src/board/contextmenu/textAlignments.tsx +++ b/frontend/src/board/contextmenu/textAlignments.tsx @@ -25,8 +25,9 @@ import { } from "../../components/Icons" import { black, disabledColor } from "../../components/UIColors" import { SubmenuProps } from "./ContextMenuView" +import { anyItemHasPermission } from "../board-permissions" -export function textAlignmentsMenu({ board, focusedItems, dispatch, permissions }: SubmenuProps) { +export function textAlignmentsMenu({ board, focusedItems, dispatch }: SubmenuProps) { const textItems = L.view(focusedItems, (items) => items.items.filter(isTextItem)) const allText = L.view(focusedItems, textItems, (f, t) => f.items.length > 0 && t.length === f.items.length) @@ -48,7 +49,8 @@ export function textAlignmentsMenu({ board, focusedItems, dispatch, permissions dispatch({ action: "item.update", boardId: b.id, items: updated }) } - const enabled = permissions.everyItemHasPermission(textItems, (p) => p.canChangeTextAlign, componentScope()) + const enabled = L.view(textItems, (items) => anyItemHasPermission(items, (p) => p.canChangeTextAlign)) + const className = enabled.pipe(L.map((e) => (e ? "icon" : "icon disabled"))) return L.view(allText, currentHAlign, currentVAlign, (all, ha, va) => { diff --git a/frontend/src/store/board-store.ts b/frontend/src/store/board-store.ts index 4bbb476c4..d1eaa3cf3 100644 --- a/frontend/src/store/board-store.ts +++ b/frontend/src/store/board-store.ts @@ -34,7 +34,6 @@ import { mkBootStrapEvent } from "../../../common/src/migration" import { BoardLocalStore, LocalStorageBoard } from "./board-local-store" import { ServerConnection } from "./server-connection" import { isLoginInProgress, UserSessionState } from "./user-session-store" -import { BoardPermissionsProvider, ItemPermissions, ConnectionPermissions } from "../board/board-permissions" export type Dispatch = (e: UIEvent) => void export type BoardStore = ReturnType export type BoardAccessStatus = @@ -503,8 +502,6 @@ export function BoardStore( }) } - const permissionsProvider: BoardPermissionsProvider = BoardPermissionsProvider(L.view(state, "accessLevel")) - return { state, events, @@ -512,7 +509,6 @@ export function BoardStore( dispatch, canUndo: undoStack.canPop, canRedo: redoStack.canPop, - permissionsProvider, } }