Skip to content

Commit

Permalink
Implement simple line
Browse files Browse the repository at this point in the history
  • Loading branch information
raimohanska committed Sep 18, 2023
1 parent 593d68b commit 6a57e3d
Show file tree
Hide file tree
Showing 11 changed files with 96 additions and 41 deletions.
1 change: 1 addition & 0 deletions common/src/domain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ export type Connection = {
fromStyle: ConnectionEndStyle
toStyle: ConnectionEndStyle
pointStyle: "none" | "black-dot"
action: "connect" | "line"
}
export type ConnectionEndPoint = Point | ConnectionEndPointToItem
export type ConnectionEndPointToItem = Id | ConectionEndPointDirectedToItem
Expand Down
8 changes: 6 additions & 2 deletions common/src/migration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,12 @@ export function migrateBoard(origBoard: Board) {
}

function migrateConnection(c: Connection): Connection {
if (c.fromStyle && c.fromStyle !== ("white-dot" as any) && c.toStyle && c.pointStyle) return c
return { ...c, fromStyle: "black-dot", toStyle: "arrow", pointStyle: "black-dot" }
c =
c.fromStyle && c.fromStyle !== ("white-dot" as any) && c.toStyle && c.pointStyle
? c
: { ...c, fromStyle: "black-dot", toStyle: "arrow", pointStyle: "black-dot" }
c = c.action !== undefined ? c : { ...c, action: "connect" }
return c
}

function migrateItem(item: Item, migratedItems: Item[], boardItems: Record<string, Item>): Item {
Expand Down
1 change: 1 addition & 0 deletions frontend/src/board/BoardView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ export const BoardView = ({
},
)
installKeyboardShortcut(plainKey("c"), () => toolController.tool.set("connect"))
installKeyboardShortcut(plainKey("l"), () => toolController.tool.set("line"))
L.fromEvent<JSX.KeyboardEvent>(window, "click")
.pipe(L.applyScope(componentScope()))
.forEach((event) => {
Expand Down
10 changes: 7 additions & 3 deletions frontend/src/board/board-drag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export type DragAction =
| { action: "select"; selectedAtStart: BoardFocus }
| { action: "pan" }
| { action: "none" }
| { action: "connect"; startPos: Point }
| { action: "connect" | "line"; startPos: Point }

export function boardDragHandler({
boardElem,
Expand Down Expand Up @@ -58,6 +58,8 @@ export function boardDragHandler({
current.set(pos)
if (t === "connect") {
dragAction.set({ action: "connect", startPos: pos })
} else if (t === "line") {
dragAction.set({ action: "line", startPos: pos })
} else if (!shouldDragSelect) {
dragAction.set({ action: "pan" })
} else {
Expand Down Expand Up @@ -113,7 +115,9 @@ export function boardDragHandler({
c.x - s.x,
)}px, ${coordinateHelper.emToPagePx(c.y - s.y)}px)`)
} else if (da.action === "connect") {
connector.whileDragging(da.startPos, coords)
connector.whileDragging(da.startPos, coords, "connect")
} else if (da.action === "line") {
connector.whileDragging(da.startPos, coords, "line")
}
},
15,
Expand All @@ -136,7 +140,7 @@ export function boardDragHandler({
const xDiff = coordinateHelper.emToPagePx(startX - x)
const yDiff = coordinateHelper.emToPagePx(startY - y)
s.scrollBy(xDiff, yDiff)
} else if (da.action === "connect") {
} else if (da.action === "connect" || da.action === "line") {
toolController.useDefaultTool()
connector.endDrag()
}
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/board/contextmenu/connection-ends.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ function nextStyle(style: ConnectionEndStyle) {

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] : null))
const singleConnection = L.view(connections, (connections) =>
connections.length === 1 && connections[0].action === "connect" ? connections[0] : null,
)
return L.view(singleConnection, (connection) => {
if (!connection) return []
return !connection
Expand Down
86 changes: 56 additions & 30 deletions frontend/src/board/item-connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
isContainedBy,
isItem,
isItemEndPoint,
isPoint,
Item,
Point,
} from "../../../common/src/domain"
Expand Down Expand Up @@ -48,10 +49,11 @@ export function startConnecting(
focus.set({ status: "connection-adding" })
const toWatch = [currentConnectionHandler, toolController.tool, focus] as L.Property<any>[]
const stop = L.merge(toWatch.map((p) => p.pipe(L.changes))).pipe(L.take(1, globalScope))
const action = toolController.tool.get() === "line" ? "line" : "connect"

h.whileDragging(from, coordinateHelper.currentBoardCoordinates.get())
h.whileDragging(from, coordinateHelper.currentBoardCoordinates.get(), action)
coordinateHelper.currentBoardCoordinates.pipe(L.takeUntil(stop)).forEach((pos) => {
h.whileDragging(from, pos)
h.whileDragging(from, pos, action)
})
stop.forEach(endConnection)
}
Expand All @@ -76,15 +78,17 @@ export function newConnectionCreator(
) {
let localConnection: Connection | null = null

function whileDragging(from: Item | Point, currentBoardCoords: Point) {
function whileDragging(from: Item | Point, currentBoardCoords: Point, action: "connect" | "line") {
const b = board.get()
const boardId = b.id
const startPoint: ConnectionEndPoint = isItem(from) ? from.id : from
const item = isItem(from) ? from : null
const target = findTarget(b, startPoint, currentBoardCoords, localConnection, {
allowConnect: action === "connect",
allowSnap: action === "line",
})

const targetExistingItem = findTarget(b, startPoint, currentBoardCoords, localConnection)

if (targetExistingItem === item) {
if (target === item?.id) {
if (localConnection !== null) {
// Remove current connection, because connect-to-self is not allowed at least for now
if (!IS_TOUCHSCREEN)
Expand All @@ -94,7 +98,7 @@ export function newConnectionCreator(
} else {
if (localConnection === null) {
// Start new connection
localConnection = newConnection(startPoint)
localConnection = newConnection(startPoint, action)
if (!IS_TOUCHSCREEN) dispatch({ action: "connection.add", boardId, connections: [localConnection] })
} else {
// Change current connection endpoint
Expand All @@ -103,7 +107,7 @@ export function newConnectionCreator(
localConnection = rerouteConnection(
{
...localConnection,
to: targetExistingItem ? targetExistingItem.id : currentBoardCoords,
to: target,
},
b,
)
Expand All @@ -113,17 +117,27 @@ export function newConnectionCreator(
}
}

function newConnection(from: ConnectionEndPoint): Connection {
function newConnection(from: ConnectionEndPoint, action: "connect" | "line"): Connection {
const l = latestConnection.get()

return rerouteConnection(
{
id: uuid.v4(),
from: from,
controlPoints: l && l.controlPoints.length === 0 ? [] : [{ x: 0, y: 0 }],
to: targetExistingItem ? targetExistingItem.id : currentBoardCoords,
fromStyle: (l && l.fromStyle) ?? "none",
toStyle: (l && l.toStyle) ?? "arrow",
pointStyle: (l && l.pointStyle) ?? "black-dot",
to: target,
action,
...(action === "connect"
? {
fromStyle: (l && l.fromStyle) ?? "none",
toStyle: (l && l.toStyle) ?? "arrow",
pointStyle: (l && l.pointStyle) ?? "black-dot",
}
: {
fromStyle: "none",
toStyle: "none",
pointStyle: "none",
}),
},
b,
)
Expand All @@ -150,6 +164,10 @@ export function newConnectionCreator(
}
}

function shouldPreventAttach(e: DragEvent) {
return e.shiftKey || e.altKey || e.ctrlKey || e.metaKey
}

export function existingConnectionHandler(
endNode: HTMLElement,
connectionId: string,
Expand All @@ -162,8 +180,7 @@ export function existingConnectionHandler(
endNode.addEventListener(
"drag",
_.throttle((e: DragEvent) => {
const preventAttach = e.shiftKey || e.altKey || e.ctrlKey || e.metaKey
modifyConnection(preventAttach)
modifyConnection(shouldPreventAttach(e))
}, 20) as any,
)

Expand All @@ -180,17 +197,21 @@ export function existingConnectionHandler(
const b = board.get()
const connection = b.connections.find((c) => c.id === connectionId)!
const coords = coordinateHelper.currentBoardCoordinates.get()
const options = {
allowConnect: connection.action === "connect" && !preventAttach,
allowSnap: connection.action === "line" && !preventAttach,
}
if (type === "to") {
const hitsItem = !preventAttach && findTarget(b, connection.from, coords, connection)
const to = hitsItem && hitsItem.id !== connection.from ? hitsItem.id : coords
const target = findTarget(b, connection.from, coords, connection, options)
const to = target !== connection.from ? target : coords
dispatch({
action: "connection.modify",
boardId: b.id,
connections: [rerouteConnection({ ...connection, to }, b)],
})
} else if (type === "from") {
const hitsItem = !preventAttach && findTarget(b, connection.to, coords, connection)
const from = hitsItem && hitsItem.id !== connection.to ? hitsItem.id : coords
const target = findTarget(b, connection.to, coords, connection, options)
const from = target !== connection.to ? target : coords
dispatch({
action: "connection.modify",
boardId: b.id,
Expand All @@ -211,21 +232,26 @@ function findTarget(
from: Item | ConnectionEndPoint,
currentPos: Point,
currentConnection: Connection | null,
) {
options: { allowConnect: boolean; allowSnap: boolean },
): Id | Point {
const items = b.items
const resolvedFromPoint = resolveEndpoint(from, items)
const fromItem = isItem(resolvedFromPoint) ? resolvedFromPoint : null

return Object.values(items)
.filter((i) => containedBy({ ...currentPos, width: 0, height: 0 }, i)) // match coordinates
.filter((i) => isConnectionAttachmentPoint(currentPos, i))
.filter((i) =>
isItem(resolvedFromPoint)
? !isConnected(b, i, resolvedFromPoint, currentConnection)
: !containedBy(resolvedFromPoint, i),
)
.sort((a, b) => (isContainedBy(items, a)(b) ? 1 : -1)) // most innermost first (containers last)
.find((i) => !fromItem || !isContainedBy(items, i)(fromItem)) // does not contain the "from" item
const targetItem =
options.allowConnect &&
Object.values(items)
.filter((i) => containedBy({ ...currentPos, width: 0, height: 0 }, i)) // match coordinates
.filter((i) => isConnectionAttachmentPoint(currentPos, i))
.filter((i) =>
isItem(resolvedFromPoint)
? !isConnected(b, i, resolvedFromPoint, currentConnection)
: !containedBy(resolvedFromPoint, i),
)
.sort((a, b) => (isContainedBy(items, a)(b) ? 1 : -1)) // most innermost first (containers last)
.find((i) => !fromItem || !isContainedBy(items, i)(fromItem)) // does not contain the "from" item

return targetItem || currentPos
}

function isConnected(b: Board, x: Item, y: Item, connectionToIgnore: Connection | null) {
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/board/item-dragmove.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ export function itemDragToMove(

// TODO: disable multiple selection in connect mode

if (t === "connect") {
if (t === "connect" || t === "line") {
const { current, dragStartPosition } = items[0]
const from = isConnectionAttachmentPoint(startPos, current) ? current : startPos
connector.whileDragging(from, coordinateHelper.currentBoardCoordinates.get())
connector.whileDragging(from, coordinateHelper.currentBoardCoordinates.get(), t)
} else {
const margin = BOARD_ITEM_BORDER_MARGIN
const movedItems = items.map(({ dragStartPosition, current }) => {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/board/tool-selection.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { localStorageAtom } from "./local-storage-atom"
import * as L from "lonna"

export type Tool = "pan" | "select" | "connect" | "note" | "container" | "text"
export type Tool = "pan" | "select" | "connect" | "note" | "container" | "text" | "line"
export type ControlSettings = {
tool: Tool
defaultTool?: Tool
Expand Down
3 changes: 1 addition & 2 deletions frontend/src/board/toolbars/MainToolBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,11 @@ import { BoardCoordinateHelper } from "../board-coordinates"
import { BoardFocus, getSelectedConnectionIds, getSelectedItemIds } from "../board-focus"
import { dispatchDeletion } from "../item-delete"
import { DND_GHOST_HIDING_IMAGE } from "../item-drag"
import { dispatchDuplication } from "../item-duplicate"
import { localStorageAtom } from "../local-storage-atom"
import { ToolController } from "../tool-selection"
import { IS_TOUCHSCREEN } from "../touchScreen"
import { PaletteView } from "./PaletteView"
import { ToolSelector } from "./ToolSelector"
import { dispatchDuplication } from "../item-duplicate"

export const MainToolBar = ({
coordinateHelper,
Expand Down
12 changes: 12 additions & 0 deletions frontend/src/board/toolbars/ToolSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,18 @@ export const ToolSelector = ({ toolController }: { toolController: ToolControlle
),
}}
/>
<ToolIcon
{...{
name: "line",
tooltip: "Line tool",
currentTool: tool,
svg: (c) => (
<svg viewBox="0 0 24 24">
<path fill-rule="evenodd" d="M4 11h16v2H4z"></path>
</svg>
),
}}
/>
</>
)
}
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/style/tool-layer.scss
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,12 @@
}
}

.line {
@media (max-width: 500px) {
display: none;
}
}

.tool.undo,
.tool.redo {
@media (min-width: $non-narrow-screen-breakpoint) {
Expand Down

0 comments on commit 6a57e3d

Please sign in to comment.