Skip to content

Commit

Permalink
Implement horizontal text alignment in context menu
Browse files Browse the repository at this point in the history
  • Loading branch information
raimohanska committed Sep 17, 2023
1 parent 85b44e6 commit 2e259c1
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 10 deletions.
33 changes: 32 additions & 1 deletion common/src/domain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ export const ITEM_TYPES = {
CONTAINER: "container",
} as const
export type ItemType = typeof ITEM_TYPES[keyof typeof ITEM_TYPES]
export type TextItemProperties = ItemProperties & { text: string; fontSize?: number }
export type TextItemProperties = ItemProperties & { text: string; fontSize?: number; align?: Align }
export type NoteShape = "round" | "square" | "rect" | "diamond"
export type Note = TextItemProperties & {
type: typeof ITEM_TYPES.NOTE
Expand Down Expand Up @@ -621,3 +621,34 @@ export function canRead(a: AccessLevel) {
export function canWrite(a: AccessLevel) {
return a === "read-write" || a === "admin"
}

export type Align = "TL" | "TC" | "TR" | "ML" | "MC" | "MR" | "BL" | "BC" | "BR"

export function getAlign(item: TextItem) {
return item.align ?? (isNote(item) ? "MC" : "TL")
}

export type HorizontalAlign = "left" | "center" | "right"
export function getHorizontalAlign(item: TextItem): HorizontalAlign {
switch (getAlign(item)) {
case "TL":
case "ML":
case "BL":
return "left"
case "TC":
case "MC":
case "BC":
return "center"
case "TR":
case "MR":
case "BR":
return "right"
}
console.log("Unknown align", getAlign(item))
return "center"
}
export function setHorizontalAlign<I extends TextItem>(item: I, a: HorizontalAlign): I {
const letter = a === "left" ? "L" : a === "center" ? "C" : "R"
const align = `${getAlign(item)[0]}${letter}`
return { ...item, align }
}
25 changes: 23 additions & 2 deletions frontend/src/board/ItemView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import {
BoardHistoryEntry,
canWrite,
Connection,
getHorizontalAlign,
getItemBackground,
getItemIds,
getItemShape,
Id,
isTextItem,
Item,
ItemType,
TextItem,
Expand Down Expand Up @@ -121,6 +123,8 @@ export const ItemView = ({
zIndex: itemZIndex(i),
position: "absolute",
padding: itemPadding(i),
justifyContent: getJustifyContent(i),
textAlign: getTextAlign(i),
}
})}
>
Expand Down Expand Up @@ -193,6 +197,23 @@ export const ItemView = ({
}
}

function findItemHistory(history: BoardHistoryEntry[], id: Id): BoardHistoryEntry[] {
return history.filter((e) => getItemIds(e).includes(id))
function getJustifyContent(item: Item) {
if (isTextItem(item)) {
switch (getHorizontalAlign(item)) {
case "left":
return "flex-start"
case "center":
return "center"
case "right":
return "flex-end"
}
}
return null
}

function getTextAlign(item: Item) {
if (isTextItem(item)) {
return getHorizontalAlign(item)
}
return null
}
2 changes: 2 additions & 0 deletions frontend/src/board/contextmenu/ContextMenuView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { colorsAndShapesMenu } from "./colorsAndShapes"
import { fontSizesMenu } from "./fontSizes"
import { resolveEndpoint } from "../../../../common/src/connection-utils"
import { connectionEndsMenu } from "./connection-ends"
import { textAlignmentsMenu } from "./textAlignments"

export type SubmenuProps = {
focusedItems: L.Property<{ items: Item[]; connections: Connection[] }>
Expand Down Expand Up @@ -97,6 +98,7 @@ export const ContextMenuView = ({
alignmentsMenu("y", props),
colorsAndShapesMenu(props),
fontSizesMenu(props),
textAlignmentsMenu(props),
areaTilingMenu(props),
connectionEndsMenu(props),
]
Expand Down
13 changes: 6 additions & 7 deletions frontend/src/board/contextmenu/alignments.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { h, HarmajaOutput } from "harmaja"
import { h } from "harmaja"
import _ from "lodash"
import * as L from "lonna"
import { Board, Item } from "../../../../common/src/domain"
import { Item } from "../../../../common/src/domain"
import {
AlignHorizontalCenterIcon,
AlignHorizontalLeftIcon,
Expand All @@ -12,7 +12,6 @@ import {
HorizontalDistributeIcon,
VerticalDistributeIcon,
} from "../../components/Icons"
import { Dispatch } from "../../store/board-store"
import { SubmenuProps } from "./ContextMenuView"

const createSubMenuByAxis = (axis: Axis) => (props: SubmenuProps) => {
Expand Down Expand Up @@ -138,7 +137,7 @@ export function alignmentsSubMenu(axis: Axis, props: SubmenuProps) {
<span
className="icon"
onClick={() => moveFocusedItems("x", getCenterCoordinate, props)}
title="Align left"
title="Align center"
>
<AlignHorizontalCenterIcon />
</span>
Expand All @@ -148,7 +147,7 @@ export function alignmentsSubMenu(axis: Axis, props: SubmenuProps) {
<span
className="icon"
onClick={() => moveFocusedItems("x", getMaxCoordinate, props)}
title="Align left"
title="Align right"
>
<AlignHorizontalRightIcon />
</span>
Expand Down Expand Up @@ -180,7 +179,7 @@ export function alignmentsSubMenu(axis: Axis, props: SubmenuProps) {
{hasItemsToAlign && (
<span
className="icon"
title="Align top"
title="Align middle"
onClick={() => moveFocusedItems("y", getCenterCoordinate, props)}
>
<AlignVerticalCenterIcon />
Expand All @@ -190,7 +189,7 @@ export function alignmentsSubMenu(axis: Axis, props: SubmenuProps) {
{hasItemsToAlign && (
<span
className="icon"
title="Align top"
title="Align bottom"
onClick={() => moveFocusedItems("y", getMaxCoordinate, props)}
>
<AlignVerticalBottomIcon />
Expand Down
67 changes: 67 additions & 0 deletions frontend/src/board/contextmenu/textAlignments.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { HarmajaOutput, h } from "harmaja"
import * as L from "lonna"
import {
Color,
HorizontalAlign,
Item,
getHorizontalAlign,
isTextItem,
setHorizontalAlign,
} from "../../../../common/src/domain"
import {
TextAlignHorizontalCenterIcon,
TextAlignHorizontalLeftIcon,
TextAlignHorizontalRightIcon,
} from "../../components/Icons"
import { black } from "../../components/UIColors"
import { SubmenuProps } from "./ContextMenuView"

export function textAlignmentsMenu({ board, focusedItems, dispatch, submenu }: 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)

const currentAlign = L.view(focusedItems, (f) => {
const items = f.items
const align = getHA(items[0])
for (let i = 1; i < items.length; i++) {
if (getHA(items[i]) !== align) {
return null // If not all share the same, return null
}
}
return align
})

function setAlign(align: HorizontalAlign) {
focusedItems.get()
const b = board.get()
const updated = focusedItems.get().items.map((i) => (isTextItem(i) ? setHorizontalAlign(i, align) : i))
dispatch({ action: "item.update", boardId: b.id, items: updated })
}

return L.view(allText, currentAlign, (all, ca) => {
return !all
? []
: [
<div className="icon-group text-align">
<span
className="icon"
onClick={() => setAlign(aligns[(aligns.indexOf(ca ?? "left") + 1) % aligns.length])}
title="Align left"
>
{icons[ca ?? "left"](black)}
</span>
</div>,
]
})
}

function getHA(item: Item | null) {
return item && isTextItem(item) ? getHorizontalAlign(item) : null
}

const icons: Record<HorizontalAlign, (color: Color) => HarmajaOutput> = {
left: (color: Color) => <TextAlignHorizontalLeftIcon color={color} />,
center: (color: Color) => <TextAlignHorizontalCenterIcon color={color} />,
right: (color: Color) => <TextAlignHorizontalRightIcon color={color} />,
}
const aligns: HorizontalAlign[] = ["left", "center", "right"]
18 changes: 18 additions & 0 deletions frontend/src/components/Icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,24 @@ export const RedoIcon = ({ enabled }: { enabled: L.Property<boolean> }) => {
)
}

export const TextAlignHorizontalLeftIcon = ({ color }: { color: Color }) => (
<svg viewBox="0 0 24 24">
<path fill={color} d="M15 15H3v2h12v-2zm0-8H3v2h12V7zM3 13h18v-2H3v2zm0 8h18v-2H3v2zM3 3v2h18V3H3z"></path>
</svg>
)

export const TextAlignHorizontalRightIcon = ({ color }: { color: Color }) => (
<svg viewBox="0 0 24 24">
<path fill={color} d="M3 21h18v-2H3v2zm6-4h12v-2H9v2zm-6-4h18v-2H3v2zm6-4h12V7H9v2zM3 3v2h18V3H3z"></path>
</svg>
)

export const TextAlignHorizontalCenterIcon = ({ color }: { color: Color }) => (
<svg viewBox="0 0 24 24">
<path fill={color} d="M7 15v2h10v-2H7zm-4 6h18v-2H3v2zm0-8h18v-2H3v2zm4-6v2h10V7H7zM3 3v2h18V3H3z"></path>
</svg>
)

export const AlignHorizontalLeftIcon = () => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M4 22H2V2h2v20zM22 7H6v3h16V7zm-6 7H6v3h10v-3z" />
Expand Down

0 comments on commit 2e259c1

Please sign in to comment.